refactor(connlib): introduce dedicated Resource model (#6920)

Following up from #6919, this PR introduces a dedicated, internal model
for resources as to how the client uses them. This separation serves
several purposes:

1. It allows us to introduce an `Unknown` resource type, ensuring
forwards-compatibility with future resource types.
2. It allows us to remove trait implementations like `PartialEq` or
`PartialOrd` from the message types. With #6732, the messages will
include types like `SecretKey`s which cannot be compared.
3. A decoupling of serialisation and domain models is good practice in
general and has long been overdue.
This commit is contained in:
Thomas Eizinger
2024-10-04 08:15:53 +10:00
committed by GitHub
parent 2fa0ab21d1
commit 1114838bb6
13 changed files with 554 additions and 986 deletions

1
rust/Cargo.lock generated
View File

@@ -2470,7 +2470,6 @@ dependencies = [
"ip_network_table",
"itertools 0.13.0",
"lru",
"phoenix-channel",
"proptest",
"proptest-state-machine",
"rand 0.8.5",

View File

@@ -41,7 +41,6 @@ uuid = { version = "1.10", default-features = false, features = ["std", "v4"] }
derivative = "2.2.0"
firezone-relay = { workspace = true, features = ["proptest"] }
ip-packet = { workspace = true, features = ["proptest"] }
phoenix-channel = { workspace = true }
proptest-state-machine = "0.3"
rand = "0.8"
serde_json = "1.0"

View File

@@ -1,8 +1,14 @@
mod resource;
pub(crate) use resource::{CidrResource, Resource};
#[cfg(all(feature = "proptest", test))]
pub(crate) use resource::{DnsResource, InternetResource};
use crate::dns::StubResolver;
use crate::messages::client::ResourceDescription;
use crate::messages::ResolveRequest;
use crate::messages::{
client::ResourceDescription, client::ResourceDescriptionCidr, Answer, DnsServer,
Interface as InterfaceConfig, IpDnsServer, Key, Offer, Relay,
Answer, DnsServer, Interface as InterfaceConfig, IpDnsServer, Key, Offer, Relay,
};
use crate::peer_store::PeerStore;
use crate::{dns, TunConfig};
@@ -72,7 +78,12 @@ const MAX_REMEMBERED_GATEWAYS: NonZeroUsize = unsafe { NonZeroUsize::new_uncheck
impl ClientTunnel {
pub fn set_resources(&mut self, resources: Vec<ResourceDescription>) {
self.role_state.set_resources(resources);
self.role_state.set_resources(
resources
.into_iter()
.filter_map(Resource::from_description)
.collect(),
);
self.role_state
.buffered_events
@@ -97,6 +108,10 @@ impl ClientTunnel {
/// 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
@@ -207,11 +222,11 @@ pub struct ClientState {
sites_status: HashMap<SiteId, ResourceStatus>,
/// All CIDR resources we know about, indexed by the IP range they cover (like `1.1.0.0/8`).
active_cidr_resources: IpNetworkTable<ResourceDescriptionCidr>,
active_cidr_resources: IpNetworkTable<CidrResource>,
/// `Some` if the Internet resource is enabled.
internet_resource: Option<ResourceId>,
/// All resources indexed by their ID.
resources_by_id: BTreeMap<ResourceId, ResourceDescription>,
resources_by_id: BTreeMap<ResourceId, Resource>,
/// The DNS resolvers configured on the system outside of connlib.
system_resolvers: Vec<IpAddr>,
@@ -329,7 +344,7 @@ impl ClientState {
.collect_vec()
}
fn resource_status(&self, resource: &ResourceDescription) -> ResourceStatus {
fn resource_status(&self, resource: &Resource) -> ResourceStatus {
if resource.sites().iter().any(|s| {
self.sites_status
.get(&s.id)
@@ -389,10 +404,7 @@ impl ClientState {
}
fn is_dns_resource(&self, resource: &ResourceId) -> bool {
matches!(
self.resources_by_id.get(resource),
Some(ResourceDescription::Dns(_))
)
matches!(self.resources_by_id.get(resource), Some(Resource::Dns(_)))
}
fn is_cidr_resource_connected(&self, resource: &ResourceId) -> bool {
@@ -940,11 +952,11 @@ impl ClientState {
self.maybe_update_tun_config(new_tun_config);
}
fn recalculate_active_cidr_resources(&self) -> IpNetworkTable<ResourceDescriptionCidr> {
let mut active_cidr_resources = IpNetworkTable::<ResourceDescriptionCidr>::new();
fn recalculate_active_cidr_resources(&self) -> IpNetworkTable<CidrResource> {
let mut active_cidr_resources = IpNetworkTable::<CidrResource>::new();
for resource in self.resources_by_id.values() {
let ResourceDescription::Cidr(resource) = resource else {
let Resource::Cidr(resource) = resource else {
continue;
};
@@ -1080,7 +1092,7 @@ 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<ResourceDescription>) {
pub(crate) fn set_resources(&mut self, new_resources: Vec<Resource>) {
let current_resource_ids = self
.resources_by_id
.keys()
@@ -1103,7 +1115,7 @@ impl ClientState {
self.maybe_update_tun_routes();
}
pub(crate) fn add_resource(&mut self, new_resource: ResourceDescription) {
pub(crate) fn add_resource(&mut self, new_resource: Resource) {
if let Some(resource) = self.resources_by_id.get(&new_resource.id()) {
if resource.has_different_address(&new_resource) {
self.remove_resource(resource.id());
@@ -1118,10 +1130,8 @@ impl ClientState {
}
let added = match &new_resource {
ResourceDescription::Dns(dns) => {
self.stub_resolver.add_resource(dns.id, dns.address.clone())
}
ResourceDescription::Cidr(cidr) => {
Resource::Dns(dns) => self.stub_resolver.add_resource(dns.id, dns.address.clone()),
Resource::Cidr(cidr) => {
let existing = self.active_cidr_resources.exact_match(cidr.address);
match existing {
@@ -1129,7 +1139,7 @@ impl ClientState {
None => true,
}
}
ResourceDescription::Internet(resource) => {
Resource::Internet(resource) => {
self.internet_resource.replace(resource.id) != Some(resource.id)
}
};
@@ -1160,9 +1170,9 @@ impl ClientState {
};
match resource {
ResourceDescription::Dns(_) => self.stub_resolver.remove_resource(id),
ResourceDescription::Cidr(_) => {}
ResourceDescription::Internet(_) => {
Resource::Dns(_) => self.stub_resolver.remove_resource(id),
Resource::Cidr(_) => {}
Resource::Internet(_) => {
if self.internet_resource.is_some_and(|r_id| r_id == id) {
self.internet_resource = None;
}
@@ -1279,11 +1289,11 @@ fn peer_by_resource_mut<'p>(
}
fn get_addresses_for_awaiting_resource(
desc: &ResourceDescription,
desc: &Resource,
awaiting_connection_details: &AwaitingConnectionDetails,
) -> Vec<IpNetwork> {
match desc {
ResourceDescription::Dns(_) => awaiting_connection_details
Resource::Dns(_) => awaiting_connection_details
.domain
.as_ref()
.expect("for dns resources the awaiting connection should have an ip")
@@ -1292,8 +1302,8 @@ fn get_addresses_for_awaiting_resource(
.copied()
.map_into()
.collect_vec(),
ResourceDescription::Cidr(r) => vec![r.address],
ResourceDescription::Internet(_) => vec![
Resource::Cidr(r) => vec![r.address],
Resource::Internet(_) => vec![
Ipv4Network::DEFAULT_ROUTE.into(),
Ipv6Network::DEFAULT_ROUTE.into(),
],
@@ -1595,21 +1605,21 @@ mod tests {
#[cfg(all(test, feature = "proptest"))]
mod proptests {
use super::*;
use crate::messages::client::ResourceDescriptionDns;
use crate::proptest::*;
use connlib_model::ResourceView;
use prop::collection;
use proptest::prelude::*;
use resource::DnsResource;
#[test_strategy::proptest]
fn cidr_resources_are_turned_into_routes(
#[strategy(cidr_resource())] resource1: ResourceDescriptionCidr,
#[strategy(cidr_resource())] resource2: ResourceDescriptionCidr,
#[strategy(cidr_resource())] resource1: CidrResource,
#[strategy(cidr_resource())] resource2: CidrResource,
) {
let mut client_state = ClientState::for_test();
client_state.add_resource(ResourceDescription::Cidr(resource1.clone()));
client_state.add_resource(ResourceDescription::Cidr(resource2.clone()));
client_state.add_resource(Resource::Cidr(resource1.clone()));
client_state.add_resource(Resource::Cidr(resource2.clone()));
assert_eq!(
hashset(client_state.routes()),
@@ -1619,14 +1629,14 @@ mod proptests {
#[test_strategy::proptest]
fn added_resources_show_up_as_resoucres(
#[strategy(cidr_resource())] resource1: ResourceDescriptionCidr,
#[strategy(dns_resource())] resource2: ResourceDescriptionDns,
#[strategy(cidr_resource())] resource3: ResourceDescriptionCidr,
#[strategy(cidr_resource())] resource1: CidrResource,
#[strategy(dns_resource())] resource2: DnsResource,
#[strategy(cidr_resource())] resource3: CidrResource,
) {
let mut client_state = ClientState::for_test();
client_state.add_resource(ResourceDescription::Cidr(resource1.clone()));
client_state.add_resource(ResourceDescription::Dns(resource2.clone()));
client_state.add_resource(Resource::Cidr(resource1.clone()));
client_state.add_resource(Resource::Dns(resource2.clone()));
assert_eq!(
hashset(client_state.resources()),
@@ -1636,7 +1646,7 @@ mod proptests {
])
);
client_state.add_resource(ResourceDescription::Cidr(resource3.clone()));
client_state.add_resource(Resource::Cidr(resource3.clone()));
assert_eq!(
hashset(client_state.resources()),
@@ -1650,18 +1660,18 @@ mod proptests {
#[test_strategy::proptest]
fn adding_same_resource_with_different_address_updates_the_address(
#[strategy(cidr_resource())] resource: ResourceDescriptionCidr,
#[strategy(cidr_resource())] resource: CidrResource,
#[strategy(any_ip_network(8))] new_address: IpNetwork,
) {
let mut client_state = ClientState::for_test();
client_state.add_resource(ResourceDescription::Cidr(resource.clone()));
client_state.add_resource(Resource::Cidr(resource.clone()));
let updated_resource = ResourceDescriptionCidr {
let updated_resource = CidrResource {
address: new_address,
..resource
};
client_state.add_resource(ResourceDescription::Cidr(updated_resource.clone()));
client_state.add_resource(Resource::Cidr(updated_resource.clone()));
assert_eq!(
hashset(client_state.resources()),
@@ -1677,13 +1687,13 @@ mod proptests {
#[test_strategy::proptest]
fn adding_cidr_resource_with_same_id_as_dns_resource_replaces_dns_resource(
#[strategy(dns_resource())] resource: ResourceDescriptionDns,
#[strategy(dns_resource())] resource: DnsResource,
#[strategy(any_ip_network(8))] address: IpNetwork,
) {
let mut client_state = ClientState::for_test();
client_state.add_resource(ResourceDescription::Dns(resource.clone()));
client_state.add_resource(Resource::Dns(resource.clone()));
let dns_as_cidr_resource = ResourceDescriptionCidr {
let dns_as_cidr_resource = CidrResource {
address,
id: resource.id,
name: resource.name,
@@ -1691,7 +1701,7 @@ mod proptests {
sites: resource.sites,
};
client_state.add_resource(ResourceDescription::Cidr(dns_as_cidr_resource.clone()));
client_state.add_resource(Resource::Cidr(dns_as_cidr_resource.clone()));
assert_eq!(
hashset(client_state.resources()),
@@ -1707,12 +1717,12 @@ mod proptests {
#[test_strategy::proptest]
fn resources_can_be_removed(
#[strategy(dns_resource())] dns_resource: ResourceDescriptionDns,
#[strategy(cidr_resource())] cidr_resource: ResourceDescriptionCidr,
#[strategy(dns_resource())] dns_resource: DnsResource,
#[strategy(cidr_resource())] cidr_resource: CidrResource,
) {
let mut client_state = ClientState::for_test();
client_state.add_resource(ResourceDescription::Dns(dns_resource.clone()));
client_state.add_resource(ResourceDescription::Cidr(cidr_resource.clone()));
client_state.add_resource(Resource::Dns(dns_resource.clone()));
client_state.add_resource(Resource::Cidr(cidr_resource.clone()));
client_state.remove_resource(dns_resource.id);
@@ -1735,18 +1745,18 @@ mod proptests {
#[test_strategy::proptest]
fn resources_can_be_replaced(
#[strategy(dns_resource())] dns_resource1: ResourceDescriptionDns,
#[strategy(dns_resource())] dns_resource2: ResourceDescriptionDns,
#[strategy(cidr_resource())] cidr_resource1: ResourceDescriptionCidr,
#[strategy(cidr_resource())] cidr_resource2: ResourceDescriptionCidr,
#[strategy(dns_resource())] dns_resource1: DnsResource,
#[strategy(dns_resource())] dns_resource2: DnsResource,
#[strategy(cidr_resource())] cidr_resource1: CidrResource,
#[strategy(cidr_resource())] cidr_resource2: CidrResource,
) {
let mut client_state = ClientState::for_test();
client_state.add_resource(ResourceDescription::Dns(dns_resource1));
client_state.add_resource(ResourceDescription::Cidr(cidr_resource1));
client_state.add_resource(Resource::Dns(dns_resource1));
client_state.add_resource(Resource::Cidr(cidr_resource1));
client_state.set_resources(vec![
ResourceDescription::Dns(dns_resource2.clone()),
ResourceDescription::Cidr(cidr_resource2.clone()),
Resource::Dns(dns_resource2.clone()),
Resource::Cidr(cidr_resource2.clone()),
]);
assert_eq!(
@@ -1764,8 +1774,8 @@ mod proptests {
#[test_strategy::proptest]
fn setting_gateway_online_sets_all_related_resources_online(
#[strategy(resources_sharing_n_sites(1))] resources_online: Vec<ResourceDescription>,
#[strategy(resources_sharing_n_sites(1))] resources_unknown: Vec<ResourceDescription>,
#[strategy(resources_sharing_n_sites(1))] resources_online: Vec<Resource>,
#[strategy(resources_sharing_n_sites(1))] resources_unknown: Vec<Resource>,
#[strategy(gateway_id())] gateway: GatewayId,
) {
let mut client_state = ClientState::for_test();
@@ -1801,7 +1811,7 @@ mod proptests {
#[test_strategy::proptest]
fn disconnecting_gateway_sets_related_resources_unknown(
#[strategy(resources_sharing_n_sites(1))] resources: Vec<ResourceDescription>,
#[strategy(resources_sharing_n_sites(1))] resources: Vec<Resource>,
#[strategy(gateway_id())] gateway: GatewayId,
) {
let mut client_state = ClientState::for_test();
@@ -1829,8 +1839,8 @@ mod proptests {
#[test_strategy::proptest]
fn setting_resource_offline_doesnt_set_all_related_resources_offline(
#[strategy(resources_sharing_n_sites(2))] multi_site_resources: Vec<ResourceDescription>,
#[strategy(resource())] single_site_resource: ResourceDescription,
#[strategy(resources_sharing_n_sites(2))] multi_site_resources: Vec<Resource>,
#[strategy(resource())] single_site_resource: Resource,
) {
let mut client_state = ClientState::for_test();
client_state.add_resource(single_site_resource.clone());
@@ -1870,22 +1880,20 @@ mod proptests {
HashSet::from_iter(val.into_iter().map(|b| b.to_owned()))
}
fn resource() -> impl Strategy<Value = ResourceDescription> {
fn resource() -> impl Strategy<Value = Resource> {
crate::proptest::resource(site().prop_map(|s| vec![s]))
}
fn cidr_resource() -> impl Strategy<Value = ResourceDescriptionCidr> {
fn cidr_resource() -> impl Strategy<Value = CidrResource> {
crate::proptest::cidr_resource(any_ip_network(8), site().prop_map(|s| vec![s]))
}
fn dns_resource() -> impl Strategy<Value = ResourceDescriptionDns> {
fn dns_resource() -> impl Strategy<Value = DnsResource> {
crate::proptest::dns_resource(site().prop_map(|s| vec![s]))
}
// Generate resources sharing 1 site
fn resources_sharing_n_sites(
num_sites: usize,
) -> impl Strategy<Value = Vec<ResourceDescription>> {
fn resources_sharing_n_sites(num_sites: usize) -> impl Strategy<Value = Vec<Resource>> {
collection::vec(site(), num_sites)
.prop_flat_map(|sites| collection::vec(crate::proptest::resource(Just(sites)), 1..=100))
}

View File

@@ -0,0 +1,206 @@
//! Internal model of resources as used by connlib's client code.
use std::collections::BTreeSet;
use connlib_model::{
CidrResourceView, DnsResourceView, InternetResourceView, ResourceId, ResourceStatus,
ResourceView, Site,
};
use ip_network::IpNetwork;
use itertools::Itertools as _;
use crate::messages::client::{
ResourceDescription, ResourceDescriptionCidr, ResourceDescriptionDns,
ResourceDescriptionInternet,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Resource {
Dns(DnsResource),
Cidr(CidrResource),
Internet(InternetResource),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct DnsResource {
/// Resource's id.
pub id: ResourceId,
/// Internal resource's domain name.
pub address: String,
/// Name of the resource.
///
/// Used only for display.
pub name: String,
pub address_description: Option<String>,
pub sites: Vec<Site>,
}
/// Description of a resource that maps to a CIDR.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct CidrResource {
/// Resource's id.
pub id: ResourceId,
/// CIDR that this resource points to.
pub address: IpNetwork,
/// Name of the resource.
///
/// Used only for display.
pub name: String,
pub address_description: Option<String>,
pub sites: Vec<Site>,
}
/// Description of an internet resource.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct InternetResource {
/// Name of the resource.
///
/// Used only for display.
pub name: String,
/// Resource's id.
pub id: ResourceId,
/// Sites for the internet resource
pub sites: Vec<Site>,
}
impl Resource {
pub fn from_description(resource: ResourceDescription) -> Option<Self> {
match resource {
ResourceDescription::Dns(i) => Some(Resource::Dns(DnsResource::from_description(i))),
ResourceDescription::Cidr(i) => Some(Resource::Cidr(CidrResource::from_description(i))),
ResourceDescription::Internet(i) => {
Some(Resource::Internet(InternetResource::from_description(i)))
}
ResourceDescription::Unknown => None,
}
}
#[cfg(all(feature = "proptest", test))]
pub fn into_dns(self) -> Option<DnsResource> {
match self {
Resource::Dns(d) => Some(d),
Resource::Cidr(_) | Resource::Internet(_) => None,
}
}
pub fn address_string(&self) -> Option<String> {
match self {
Resource::Dns(d) => Some(d.address.clone()),
Resource::Cidr(c) => Some(c.address.to_string()),
Resource::Internet(_) => None,
}
}
pub fn sites_string(&self) -> String {
self.sites().iter().map(|s| &s.name).join("|")
}
pub fn id(&self) -> ResourceId {
match self {
Resource::Dns(r) => r.id,
Resource::Cidr(r) => r.id,
Resource::Internet(r) => r.id,
}
}
pub fn sites(&self) -> BTreeSet<&Site> {
match self {
Resource::Dns(r) => BTreeSet::from_iter(r.sites.iter()),
Resource::Cidr(r) => BTreeSet::from_iter(r.sites.iter()),
Resource::Internet(r) => BTreeSet::from_iter(r.sites.iter()),
}
}
/// What the GUI clients should show as the user-friendly display name, e.g. `Firezone GitHub`
pub fn name(&self) -> &str {
match self {
Resource::Dns(r) => &r.name,
Resource::Cidr(r) => &r.name,
Resource::Internet(_) => "Internet",
}
}
pub fn has_different_address(&self, other: &Resource) -> bool {
match (self, other) {
(Resource::Dns(dns_a), Resource::Dns(dns_b)) => dns_a.address != dns_b.address,
(Resource::Cidr(cidr_a), Resource::Cidr(cidr_b)) => cidr_a.address != cidr_b.address,
(Resource::Internet(_), Resource::Internet(_)) => false,
_ => true,
}
}
pub fn with_status(self, status: ResourceStatus) -> ResourceView {
match self {
Resource::Dns(r) => ResourceView::Dns(r.with_status(status)),
Resource::Cidr(r) => ResourceView::Cidr(r.with_status(status)),
Resource::Internet(r) => ResourceView::Internet(r.with_status(status)),
}
}
}
impl CidrResource {
pub fn from_description(resource: ResourceDescriptionCidr) -> Self {
Self {
id: resource.id,
address: resource.address,
name: resource.name,
address_description: resource.address_description,
sites: resource.sites,
}
}
pub fn with_status(self, status: ResourceStatus) -> CidrResourceView {
CidrResourceView {
id: self.id,
address: self.address,
name: self.name,
address_description: self.address_description,
sites: self.sites,
status,
}
}
}
impl InternetResource {
pub fn from_description(resource: ResourceDescriptionInternet) -> Self {
Self {
name: resource.name,
id: resource.id,
sites: resource.sites,
}
}
pub fn with_status(self, status: ResourceStatus) -> InternetResourceView {
InternetResourceView {
name: self.name,
id: self.id,
sites: self.sites,
status,
}
}
}
impl DnsResource {
pub fn from_description(resource: ResourceDescriptionDns) -> Self {
Self {
id: resource.id,
address: resource.address,
name: resource.name,
address_description: resource.address_description,
sites: resource.sites,
}
}
pub fn with_status(self, status: ResourceStatus) -> DnsResourceView {
DnsResourceView {
id: self.id,
address: self.address,
name: self.name,
address_description: self.address_description,
sites: self.sites,
status,
}
}
}

View File

@@ -3,17 +3,13 @@
use crate::messages::{
GatewayResponse, Interface, Key, Relay, RelaysPresence, RequestConnection, ReuseConnection,
};
use connlib_model::{
CidrResourceView, DnsResourceView, GatewayId, InternetResourceView, ResourceId, ResourceStatus,
ResourceView, Site, SiteId,
};
use connlib_model::{GatewayId, ResourceId, Site, SiteId};
use ip_network::IpNetwork;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::{collections::BTreeSet, net::IpAddr};
/// Description of a resource that maps to a DNS record.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, Deserialize)]
pub struct ResourceDescriptionDns {
/// Resource's id.
pub id: ResourceId,
@@ -29,21 +25,8 @@ pub struct ResourceDescriptionDns {
pub sites: Vec<Site>,
}
impl ResourceDescriptionDns {
pub fn with_status(self, status: ResourceStatus) -> DnsResourceView {
DnsResourceView {
id: self.id,
address: self.address,
name: self.name,
address_description: self.address_description,
sites: self.sites,
status,
}
}
}
/// Description of a resource that maps to a CIDR.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, Deserialize)]
pub struct ResourceDescriptionCidr {
/// Resource's id.
pub id: ResourceId,
@@ -59,25 +42,12 @@ pub struct ResourceDescriptionCidr {
pub sites: Vec<Site>,
}
impl ResourceDescriptionCidr {
pub fn with_status(self, status: ResourceStatus) -> CidrResourceView {
CidrResourceView {
id: self.id,
address: self.address,
name: self.name,
address_description: self.address_description,
sites: self.sites,
status,
}
}
}
fn internet_resource_name() -> String {
"Internet Resource".to_string()
}
/// Description of an internet resource.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, Deserialize)]
pub struct ResourceDescriptionInternet {
/// Name of the resource.
///
@@ -91,110 +61,17 @@ pub struct ResourceDescriptionInternet {
pub sites: Vec<Site>,
}
impl ResourceDescriptionInternet {
pub fn with_status(self, status: ResourceStatus) -> InternetResourceView {
InternetResourceView {
name: self.name,
id: self.id,
sites: self.sites,
status,
}
}
}
impl ResourceDescription {
pub fn address_string(&self) -> Option<String> {
match self {
ResourceDescription::Dns(d) => Some(d.address.clone()),
ResourceDescription::Cidr(c) => Some(c.address.to_string()),
ResourceDescription::Internet(_) => None,
}
}
pub fn sites_string(&self) -> String {
self.sites().iter().map(|s| &s.name).join("|")
}
pub fn id(&self) -> ResourceId {
match self {
ResourceDescription::Dns(r) => r.id,
ResourceDescription::Cidr(r) => r.id,
ResourceDescription::Internet(r) => r.id,
}
}
pub fn sites(&self) -> BTreeSet<&Site> {
match self {
ResourceDescription::Dns(r) => BTreeSet::from_iter(r.sites.iter()),
ResourceDescription::Cidr(r) => BTreeSet::from_iter(r.sites.iter()),
ResourceDescription::Internet(r) => BTreeSet::from_iter(r.sites.iter()),
}
}
pub fn sites_mut(&mut self) -> &mut Vec<Site> {
match self {
ResourceDescription::Dns(r) => &mut r.sites,
ResourceDescription::Cidr(r) => &mut r.sites,
ResourceDescription::Internet(r) => &mut r.sites,
}
}
/// What the GUI clients should show as the user-friendly display name, e.g. `Firezone GitHub`
pub fn name(&self) -> &str {
match self {
ResourceDescription::Dns(r) => &r.name,
ResourceDescription::Cidr(r) => &r.name,
ResourceDescription::Internet(_) => "Internet",
}
}
pub fn has_different_address(&self, other: &ResourceDescription) -> bool {
match (self, other) {
(ResourceDescription::Dns(dns_a), ResourceDescription::Dns(dns_b)) => {
dns_a.address != dns_b.address
}
(ResourceDescription::Cidr(cidr_a), ResourceDescription::Cidr(cidr_b)) => {
cidr_a.address != cidr_b.address
}
(ResourceDescription::Internet(_), ResourceDescription::Internet(_)) => false,
_ => true,
}
}
pub fn with_status(self, status: ResourceStatus) -> ResourceView {
match self {
ResourceDescription::Dns(r) => ResourceView::Dns(r.with_status(status)),
ResourceDescription::Cidr(r) => ResourceView::Cidr(r.with_status(status)),
ResourceDescription::Internet(r) => ResourceView::Internet(r.with_status(status)),
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResourceDescription {
Dns(ResourceDescriptionDns),
Cidr(ResourceDescriptionCidr),
Internet(ResourceDescriptionInternet),
#[serde(other)]
Unknown, // Important for forwards-compatibility with future resource types.
}
impl ResourceDescription {
pub fn into_dns(self) -> Option<ResourceDescriptionDns> {
match self {
ResourceDescription::Dns(d) => Some(d),
ResourceDescription::Cidr(_) | ResourceDescription::Internet(_) => None,
}
}
pub fn into_cidr(self) -> Option<ResourceDescriptionCidr> {
match self {
ResourceDescription::Cidr(c) => Some(c),
ResourceDescription::Dns(_) | ResourceDescription::Internet(_) => None,
}
}
}
#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
#[derive(Debug, Deserialize)]
pub struct InitClient {
pub interface: Interface,
#[serde(default)]
@@ -203,12 +80,12 @@ pub struct InitClient {
pub relays: Vec<Relay>,
}
#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
#[derive(Debug, Deserialize)]
pub struct ConfigUpdate {
pub interface: Interface,
}
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize)]
pub struct ConnectionDetails {
pub resource_id: ResourceId,
pub gateway_id: GatewayId,
@@ -217,7 +94,7 @@ pub struct ConnectionDetails {
pub site_id: SiteId,
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[derive(Debug, Deserialize)]
pub struct Connect {
pub gateway_payload: GatewayResponse,
pub resource_id: ResourceId,
@@ -227,7 +104,7 @@ pub struct Connect {
// These messages are the messages that can be received
// by a client.
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case", tag = "event", content = "payload")]
pub enum IngressMessages {
Init(InitClient),
@@ -244,7 +121,7 @@ pub enum IngressMessages {
RelaysPresence(RelaysPresence),
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[derive(Debug, Serialize)]
pub struct GatewaysIceCandidates {
/// The list of gateway IDs these candidates will be broadcast to.
pub gateway_ids: Vec<GatewayId>,
@@ -252,7 +129,7 @@ pub struct GatewaysIceCandidates {
pub candidates: BTreeSet<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[derive(Debug, Deserialize)]
pub struct GatewayIceCandidates {
/// Gateway's id the ice candidates are from
pub gateway_id: GatewayId,
@@ -261,7 +138,7 @@ pub struct GatewayIceCandidates {
}
/// The replies that can arrive from the channel by a client
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum ReplyMessages {
ConnectionDetails(ConnectionDetails),
@@ -269,7 +146,7 @@ pub enum ReplyMessages {
}
// These messages can be sent from a client to a control pane
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case", tag = "event", content = "payload")]
// enum_variant_names: These are the names in the portal!
pub enum EgressMessages {
@@ -288,10 +165,6 @@ pub enum EgressMessages {
#[cfg(test)]
mod tests {
use super::*;
use crate::messages::{DnsServer, IpDnsServer, Turn};
use chrono::DateTime;
use connlib_model::Site;
use phoenix_channel::{OutboundRequestId, PhoenixMessage};
#[test]
fn can_deserialize_internet_resource() {
@@ -328,91 +201,109 @@ mod tests {
}
#[test]
fn broadcast_ice_candidates() {
let message = r#"{"topic":"client","event":"broadcast_ice_candidates","payload":{"gateway_ids":["b3d34a15-55ab-40df-994b-a838e75d65d7"],"candidates":["candidate:7031633958891736544 1 udp 50331391 35.244.108.190 53909 typ relay"]},"ref":6}"#;
let expected = PhoenixMessage::new_message(
"client",
EgressMessages::BroadcastIceCandidates(GatewaysIceCandidates {
gateway_ids: vec!["b3d34a15-55ab-40df-994b-a838e75d65d7".parse().unwrap()],
candidates: BTreeSet::from([
"candidate:7031633958891736544 1 udp 50331391 35.244.108.190 53909 typ relay"
.to_owned(),
]),
}),
Some(OutboundRequestId::for_test(6)),
);
let ingress_message = serde_json::from_str::<PhoenixMessage<_, ()>>(message).unwrap();
assert_eq!(ingress_message, expected);
}
#[test]
fn invalidate_ice_candidates_message() {
let msg = r#"{"event":"invalidate_ice_candidates","ref":null,"topic":"client","payload":{"candidates":["candidate:7854631899965427361 1 udp 1694498559 172.28.0.100 47717 typ srflx"],"gateway_id":"2b1524e6-239e-4570-bc73-70a188e12101"}}"#;
let expected = IngressMessages::InvalidateIceCandidates(GatewayIceCandidates {
gateway_id: "2b1524e6-239e-4570-bc73-70a188e12101".parse().unwrap(),
candidates: vec![
"candidate:7854631899965427361 1 udp 1694498559 172.28.0.100 47717 typ srflx"
.to_owned(),
],
});
let actual = serde_json::from_str::<IngressMessages>(msg).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn connection_ready_deserialization() {
let message = r#"{
"ref": 0,
"topic": "client",
"event": "phx_reply",
"payload": {
"status": "ok",
"response": {
"resource_id": "ea6570d1-47c7-49d2-9dc3-efff1c0c9e0b",
"gateway_public_key": "dvy0IwyxAi+txSbAdT7WKgf7K4TekhKzrnYwt5WfbSM=",
"gateway_payload": {
"ConnectionAccepted":{
"domain_response":{
"address":[
"2607:f8b0:4008:804::200e",
"142.250.64.206"
],
"domain":"google.com"
},
"ice_parameters":{
"username":"tGeqOjtGuPzPpuOx",
"password":"pMAxxTgHHSdpqHRzHGNvuNsZinLrMxwe"
}
}
},
"persistent_keepalive": 25
}
fn can_deserialize_unknown_resource() {
let resources = r#"[
{
"id": "73037362-715d-4a83-a749-f18eadd970e6",
"type": "cidr",
"name": "172.172.0.0/16",
"address": "172.172.0.0/16",
"address_description": "cidr resource",
"gateway_groups": [{"name": "test", "id": "bf56f32d-7b2c-4f5d-a784-788977d014a4"}]
},
{
"id": "03000143-e25e-45c7-aafb-144990e57dcd",
"type": "dns",
"name": "gitlab.mycorp.com",
"address": "gitlab.mycorp.com",
"address_description": "dns resource",
"gateway_groups": [{"name": "test", "id": "bf56f32d-7b2c-4f5d-a784-788977d014a4"}]
},
{
"id": "1106047c-cd5d-4151-b679-96b93da7383b",
"type": "internet",
"name": "Internet Resource",
"gateway_groups": [{"name": "test", "id": "eb94482a-94f4-47cb-8127-14fb3afa5516"}],
"not": "relevant",
"some_other": [
"field"
]
},
{
"type": "what_is_this"
}
}"#;
let _: PhoenixMessage<IngressMessages, ReplyMessages> =
serde_json::from_str(message).unwrap();
]"#;
serde_json::from_str::<Vec<ResourceDescription>>(resources).unwrap();
}
#[test]
fn config_updated() {
let m = PhoenixMessage::new_message(
"client",
IngressMessages::ConfigChanged(ConfigUpdate {
interface: Interface {
ipv4: "100.67.138.25".parse().unwrap(),
ipv6: "fd00:2021:1111::e:65ea".parse().unwrap(),
upstream_dns: vec![DnsServer::IpPort(IpDnsServer {
address: "1.1.1.1:53".parse().unwrap(),
})],
},
}),
None,
);
let message = r#"
fn can_deserialize_ice_candidates_message() {
let json = r#"{"topic":"client","event":"ice_candidates","payload":{"gateway_id":"b3d34a15-55ab-40df-994b-a838e75d65d7","candidates":["candidate:7031633958891736544 1 udp 50331391 35.244.108.190 53909 typ relay"]},"ref":6}"#;
let message = serde_json::from_str::<IngressMessages>(json).unwrap();
assert!(matches!(message, IngressMessages::IceCandidates(_)));
}
#[test]
fn can_deserialize_invalidate_ice_candidates_message() {
let json = r#"{"event":"invalidate_ice_candidates","ref":null,"topic":"client","payload":{"candidates":["candidate:7854631899965427361 1 udp 1694498559 172.28.0.100 47717 typ srflx"],"gateway_id":"2b1524e6-239e-4570-bc73-70a188e12101"}}"#;
let message = serde_json::from_str::<IngressMessages>(json).unwrap();
assert!(matches!(
message,
IngressMessages::InvalidateIceCandidates(_)
));
}
#[test]
fn can_deserialize_connect_reply() {
let json = r#"{
"resource_id": "ea6570d1-47c7-49d2-9dc3-efff1c0c9e0b",
"gateway_public_key": "dvy0IwyxAi+txSbAdT7WKgf7K4TekhKzrnYwt5WfbSM=",
"gateway_payload": {
"ConnectionAccepted":{
"domain_response":{
"address":[
"2607:f8b0:4008:804::200e",
"142.250.64.206"
],
"domain":"google.com"
},
"ice_parameters":{
"username":"tGeqOjtGuPzPpuOx",
"password":"pMAxxTgHHSdpqHRzHGNvuNsZinLrMxwe"
}
}
},
"persistent_keepalive": 25
}"#;
let message = serde_json::from_str::<ReplyMessages>(json).unwrap();
assert!(matches!(message, ReplyMessages::Connect(_)))
}
#[test]
fn can_deserialize_connection_details_reply() {
let json = r#"
{
"resource_id": "f16ecfa0-a94f-4bfd-a2ef-1cc1f2ef3da3",
"gateway_id": "73037362-715d-4a83-a749-f18eadd970e6",
"gateway_remote_ip": "172.28.0.1",
"gateway_group_id": "bf56f32d-7b2c-4f5d-a784-788977d014a4"
}"#;
let message = serde_json::from_str::<ReplyMessages>(json).unwrap();
assert!(matches!(message, ReplyMessages::ConnectionDetails(_)));
}
#[test]
fn can_deserialize_config_changed_message() {
let json = r#"
{
"event": "config_changed",
"ref": null,
@@ -431,68 +322,15 @@ mod tests {
}
}
"#;
let ingress_message: PhoenixMessage<IngressMessages, ReplyMessages> =
serde_json::from_str(message).unwrap();
assert_eq!(m, ingress_message);
let message = serde_json::from_str::<IngressMessages>(json).unwrap();
assert!(matches!(message, IngressMessages::ConfigChanged(_)))
}
#[test]
fn init_phoenix_message() {
let m = PhoenixMessage::new_message(
"client",
IngressMessages::Init(InitClient {
interface: Interface {
ipv4: "100.72.112.111".parse().unwrap(),
ipv6: "fd00:2021:1111::13:efb9".parse().unwrap(),
upstream_dns: vec![],
},
resources: vec![
ResourceDescription::Cidr(ResourceDescriptionCidr {
id: "73037362-715d-4a83-a749-f18eadd970e6".parse().unwrap(),
address: "172.172.0.0/16".parse().unwrap(),
name: "172.172.0.0/16".to_string(),
address_description: Some("cidr resource".to_string()),
sites: vec![Site {
name: "test".to_string(),
id: "bf56f32d-7b2c-4f5d-a784-788977d014a4".parse().unwrap(),
}],
}),
ResourceDescription::Cidr(ResourceDescriptionCidr {
id: "73037362-715d-4a83-a749-f18eadd970e7".parse().unwrap(),
address: "172.173.0.0/16".parse().unwrap(),
name: "172.173.0.0/16".to_string(),
address_description: None,
sites: vec![Site {
name: "test".to_string(),
id: "bf56f32d-7b2c-4f5d-a784-788977d014a4".parse().unwrap(),
}],
}),
ResourceDescription::Dns(ResourceDescriptionDns {
id: "03000143-e25e-45c7-aafb-144990e57dcd".parse().unwrap(),
address: "gitlab.mycorp.com".to_string(),
name: "gitlab.mycorp.com".to_string(),
address_description: Some("dns resource".to_string()),
sites: vec![Site {
name: "test".to_string(),
id: "bf56f32d-7b2c-4f5d-a784-788977d014a4".parse().unwrap(),
}],
}),
ResourceDescription::Dns(ResourceDescriptionDns {
id: "03000143-e25e-45c7-aafb-144990e57dce".parse().unwrap(),
address: "github.mycorp.com".to_string(),
name: "github.mycorp.com".to_string(),
address_description: None,
sites: vec![Site {
name: "test".to_string(),
id: "bf56f32d-7b2c-4f5d-a784-788977d014a4".parse().unwrap(),
}],
}),
],
relays: vec![],
}),
None,
);
let message = r#"{
fn can_deserialize_init_message() {
let json = r#"{
"event": "init",
"payload": {
"interface": {
@@ -539,320 +377,52 @@ mod tests {
"ref": null,
"topic": "client"
}"#;
let ingress_message: PhoenixMessage<IngressMessages, ReplyMessages> =
serde_json::from_str(message).unwrap();
assert_eq!(m, ingress_message);
let message = serde_json::from_str::<IngressMessages>(json).unwrap();
assert!(matches!(message, IngressMessages::Init(_)));
}
#[test]
fn messages_ignore_additional_fields() {
let m = PhoenixMessage::new_message(
"client",
IngressMessages::Init(InitClient {
interface: Interface {
ipv4: "100.72.112.111".parse().unwrap(),
ipv6: "fd00:2021:1111::13:efb9".parse().unwrap(),
upstream_dns: vec![],
},
resources: vec![
ResourceDescription::Cidr(ResourceDescriptionCidr {
id: "73037362-715d-4a83-a749-f18eadd970e6".parse().unwrap(),
address: "172.172.0.0/16".parse().unwrap(),
name: "172.172.0.0/16".to_string(),
address_description: Some("cidr resource".to_string()),
sites: vec![Site {
name: "test".to_string(),
id: "bf56f32d-7b2c-4f5d-a784-788977d014a4".parse().unwrap(),
}],
}),
ResourceDescription::Dns(ResourceDescriptionDns {
id: "03000143-e25e-45c7-aafb-144990e57dcd".parse().unwrap(),
address: "gitlab.mycorp.com".to_string(),
name: "gitlab.mycorp.com".to_string(),
address_description: Some("dns resource".to_string()),
sites: vec![Site {
name: "test".to_string(),
id: "bf56f32d-7b2c-4f5d-a784-788977d014a4".parse().unwrap(),
}],
}),
],
relays: vec![],
}),
None,
);
let message = r#"{
"event": "init",
"payload": {
"interface": {
"ipv4": "100.72.112.111",
"ipv6": "fd00:2021:1111::13:efb9",
"upstream_dns": [],
"extra_config": "foo"
},
"resources": [
{
"address": "172.172.0.0/16",
"id": "73037362-715d-4a83-a749-f18eadd970e6",
"name": "172.172.0.0/16",
"type": "cidr",
"address_description": "cidr resource",
"gateway_groups": [{"name": "test", "id": "bf56f32d-7b2c-4f5d-a784-788977d014a4"}],
"not": "relevant"
},
{
"address": "gitlab.mycorp.com",
"id": "03000143-e25e-45c7-aafb-144990e57dcd",
"ipv4": "100.126.44.50",
"ipv6": "fd00:2021:1111::e:7758",
"name": "gitlab.mycorp.com",
"type": "dns",
"address_description": "dns resource",
"gateway_groups": [{"name": "test", "id": "bf56f32d-7b2c-4f5d-a784-788977d014a4"}],
"not": "relevant"
}
]
},
"ref": null,
"topic": "client"
}"#;
let ingress_message: PhoenixMessage<IngressMessages, ReplyMessages> =
serde_json::from_str(message).unwrap();
assert_eq!(m, ingress_message);
}
#[test]
fn messages_ignore_additional_bool_fields() {
let m = PhoenixMessage::new_message(
"client",
IngressMessages::Init(InitClient {
interface: Interface {
ipv4: "100.72.112.111".parse().unwrap(),
ipv6: "fd00:2021:1111::13:efb9".parse().unwrap(),
upstream_dns: vec![],
},
resources: vec![],
relays: vec![],
}),
None,
);
let message = r#"{
"event": "init",
"payload": {
"interface": {
"ipv4": "100.72.112.111",
"ipv6": "fd00:2021:1111::13:efb9",
"upstream_dns": [],
"additional": true
},
"resources": []
},
"ref": null,
"topic": "client"
}"#;
let ingress_message: PhoenixMessage<IngressMessages, ReplyMessages> =
serde_json::from_str(message).unwrap();
assert_eq!(m, ingress_message);
}
#[test]
fn messages_ignore_additional_number_fields() {
let m = PhoenixMessage::new_message(
"client",
IngressMessages::Init(InitClient {
interface: Interface {
ipv4: "100.72.112.111".parse().unwrap(),
ipv6: "fd00:2021:1111::13:efb9".parse().unwrap(),
upstream_dns: vec![],
},
resources: vec![],
relays: vec![],
}),
None,
);
let message = r#"{
"event": "init",
"payload": {
"interface": {
"ipv4": "100.72.112.111",
"ipv6": "fd00:2021:1111::13:efb9",
"upstream_dns": [],
"additional": 0.3
},
"resources": []
},
"ref": null,
"topic": "client"
}"#;
let ingress_message: PhoenixMessage<IngressMessages, ReplyMessages> =
serde_json::from_str(message).unwrap();
assert_eq!(m, ingress_message);
}
#[test]
fn messages_ignore_additional_object_fields() {
let m = PhoenixMessage::new_message(
"client",
IngressMessages::Init(InitClient {
interface: Interface {
ipv4: "100.72.112.111".parse().unwrap(),
ipv6: "fd00:2021:1111::13:efb9".parse().unwrap(),
upstream_dns: vec![],
},
resources: vec![],
relays: vec![],
}),
None,
);
let message = r#"{
"event": "init",
"payload": {
"interface": {
"ipv4": "100.72.112.111",
"ipv6": "fd00:2021:1111::13:efb9",
"upstream_dns": [],
"additional": { "ignored": "field" }
},
"resources": []
},
"ref": null,
"topic": "client"
}"#;
let ingress_message: PhoenixMessage<IngressMessages, ReplyMessages> =
serde_json::from_str(message).unwrap();
assert_eq!(m, ingress_message);
}
#[test]
fn messages_ignore_additional_array_fields() {
let m = PhoenixMessage::new_message(
"client",
IngressMessages::Init(InitClient {
interface: Interface {
ipv4: "100.72.112.111".parse().unwrap(),
ipv6: "fd00:2021:1111::13:efb9".parse().unwrap(),
upstream_dns: vec![],
},
resources: vec![],
relays: vec![],
}),
None,
);
let message = r#"{
"event": "init",
"payload": {
"interface": {
"ipv4": "100.72.112.111",
"ipv6": "fd00:2021:1111::13:efb9",
"upstream_dns": [],
"additional": [true, false]
},
"resources": []
},
"ref": null,
"topic": "client"
}"#;
let ingress_message: PhoenixMessage<IngressMessages, ReplyMessages> =
serde_json::from_str(message).unwrap();
assert_eq!(m, ingress_message);
}
#[test]
fn list_relays_message() {
let m = PhoenixMessage::<EgressMessages, ()>::new_message(
"client",
EgressMessages::PrepareConnection {
resource_id: "f16ecfa0-a94f-4bfd-a2ef-1cc1f2ef3da3".parse().unwrap(),
connected_gateway_ids: BTreeSet::new(),
},
None,
);
let message = r#"
fn can_deserialize_relay_presence() {
let json = r#"
{
"event": "prepare_connection",
"event": "relays_presence",
"ref": null,
"topic": "client",
"payload": {
"resource_id": "f16ecfa0-a94f-4bfd-a2ef-1cc1f2ef3da3",
"connected_gateway_ids": []
},
"ref":null,
"topic": "client"
}
"#;
let egress_message = serde_json::from_str(message).unwrap();
assert_eq!(m, egress_message);
}
#[test]
fn connection_details_reply() {
let m = PhoenixMessage::<EgressMessages, ReplyMessages>::new_ok_reply(
"client",
ReplyMessages::ConnectionDetails(ConnectionDetails {
gateway_id: "73037362-715d-4a83-a749-f18eadd970e6".parse().unwrap(),
gateway_remote_ip: "172.28.0.1".parse().unwrap(),
resource_id: "f16ecfa0-a94f-4bfd-a2ef-1cc1f2ef3da3".parse().unwrap(),
site_id: "bf56f32d-7b2c-4f5d-a784-788977d014a4".parse().unwrap(),
}),
None,
);
let message = r#"
{
"ref":null,
"topic":"client",
"event": "phx_reply",
"payload": {
"response": {
"resource_id": "f16ecfa0-a94f-4bfd-a2ef-1cc1f2ef3da3",
"gateway_id": "73037362-715d-4a83-a749-f18eadd970e6",
"gateway_remote_ip": "172.28.0.1",
"gateway_group_id": "bf56f32d-7b2c-4f5d-a784-788977d014a4"
},
"status":"ok"
"disconnected_ids": [
"e95f9517-2152-4677-a16a-fbb2687050a3",
"b0724bd1-a8cc-4faf-88cd-f21159cfec47"
],
"connected": [
{
"id": "0a133356-7a9e-4b9a-b413-0d95a5720fd8",
"type": "turn",
"username": "1719367575:ZQHcVGkdnfgGmcP1",
"password": "ZWYiBeFHOJyYq0mcwAXjRpcuXIJJpzWlOXVdxwttrWg",
"addr": "172.28.0.101:3478",
"expires_at": 1719367575
}
]
}
}"#;
let reply_message = serde_json::from_str(message).unwrap();
assert_eq!(m, reply_message);
}
"#;
let message = serde_json::from_str::<IngressMessages>(json).unwrap();
assert!(matches!(message, IngressMessages::RelaysPresence(_)));
}
#[test]
fn relays_presence() {
let message = r#"
{
"event": "relays_presence",
"ref": null,
"topic": "client",
"payload": {
"disconnected_ids": [
"e95f9517-2152-4677-a16a-fbb2687050a3",
"b0724bd1-a8cc-4faf-88cd-f21159cfec47"
],
"connected": [
{
"id": "0a133356-7a9e-4b9a-b413-0d95a5720fd8",
"type": "turn",
"username": "1719367575:ZQHcVGkdnfgGmcP1",
"password": "ZWYiBeFHOJyYq0mcwAXjRpcuXIJJpzWlOXVdxwttrWg",
"addr": "172.28.0.101:3478",
"expires_at": 1719367575
}
]
}
}
"#;
let expected = IngressMessages::RelaysPresence(RelaysPresence {
disconnected_ids: vec![
"e95f9517-2152-4677-a16a-fbb2687050a3".parse().unwrap(),
"b0724bd1-a8cc-4faf-88cd-f21159cfec47".parse().unwrap(),
],
connected: vec![Relay::Turn(Turn {
id: "0a133356-7a9e-4b9a-b413-0d95a5720fd8".parse().unwrap(),
expires_at: DateTime::from_timestamp(1719367575, 0).unwrap(),
addr: "172.28.0.101:3478".parse().unwrap(),
username: "1719367575:ZQHcVGkdnfgGmcP1".to_owned(),
password: "ZWYiBeFHOJyYq0mcwAXjRpcuXIJJpzWlOXVdxwttrWg".to_owned(),
})],
});
fn serialize_prepare_connection_message() {
let message = EgressMessages::PrepareConnection {
resource_id: "f16ecfa0-a94f-4bfd-a2ef-1cc1f2ef3da3".parse().unwrap(),
connected_gateway_ids: BTreeSet::new(),
};
let expected_json = r#"{"event":"prepare_connection","payload":{"resource_id":"f16ecfa0-a94f-4bfd-a2ef-1cc1f2ef3da3","connected_gateway_ids":[]}}"#;
let actual_json = serde_json::to_string(&message).unwrap();
let ingress_message = serde_json::from_str::<IngressMessages>(message).unwrap();
assert_eq!(ingress_message, expected);
assert_eq!(actual_json, expected_json);
}
}

View File

@@ -15,7 +15,7 @@ use std::{
pub type Filters = Vec<Filter>;
/// Description of a resource that maps to a DNS record.
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize, Clone)]
pub struct ResourceDescriptionDns {
/// Resource's id.
pub id: ResourceId,
@@ -30,7 +30,7 @@ pub struct ResourceDescriptionDns {
}
/// Description of a resource that maps to a CIDR.
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize, Clone)]
pub struct ResourceDescriptionCidr {
/// Resource's id.
pub id: ResourceId,
@@ -45,12 +45,12 @@ pub struct ResourceDescriptionCidr {
}
/// Description of an Internet resource.
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize, Clone)]
pub struct ResourceDescriptionInternet {
pub id: ResourceId,
}
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResourceDescription {
Dns(ResourceDescriptionDns),
@@ -104,14 +104,14 @@ impl ResourceDescription {
}
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[derive(Debug, Deserialize, Clone)]
pub struct ClientPayload {
pub ice_parameters: Offer,
pub domain: Option<ResolveRequest>,
}
// TODO: Should this have a resource?
#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
#[derive(Debug, Deserialize, Clone)]
pub struct InitGateway {
pub interface: Interface,
pub config: Config,
@@ -125,14 +125,14 @@ pub struct Config {
pub ipv6_masquerade_enabled: bool,
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[derive(Debug, Deserialize, Clone)]
pub struct Client {
pub id: ClientId,
pub payload: ClientPayload,
pub peer: Peer,
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[derive(Debug, Deserialize, Clone)]
pub struct RequestConnection {
pub resource: ResourceDescription,
pub client: Client,
@@ -142,12 +142,12 @@ pub struct RequestConnection {
pub expires_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RemoveResource {
pub id: ResourceId,
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[derive(Debug, Deserialize, Clone)]
pub struct AllowAccess {
pub client_id: ClientId,
pub resource: ResourceDescription,
@@ -162,7 +162,7 @@ pub struct AllowAccess {
pub client_ipv6: Ipv6Addr,
}
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize, Clone)]
pub struct RejectAccess {
pub client_id: ClientId,
pub resource_id: ResourceId,
@@ -170,7 +170,7 @@ pub struct RejectAccess {
// These messages are the messages that can be received
// either by a client or a gateway by the client.
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "snake_case", tag = "event", content = "payload")]
pub enum IngressMessages {
RequestConnection(RequestConnection),
@@ -184,7 +184,7 @@ pub enum IngressMessages {
}
/// A client's ice candidate message.
#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
#[derive(Debug, Serialize, Clone)]
pub struct ClientsIceCandidates {
/// Client's id the ice candidates are meant for
pub client_ids: Vec<ClientId>,
@@ -193,7 +193,7 @@ pub struct ClientsIceCandidates {
}
/// A client's ice candidate message.
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize, Clone)]
pub struct ClientIceCandidates {
/// Client's id the ice candidates came from
pub client_id: ClientId,
@@ -221,8 +221,6 @@ pub struct ConnectionReady {
#[cfg(test)]
mod tests {
use super::*;
use crate::messages::Turn;
use phoenix_channel::PhoenixMessage;
#[test]
fn can_deserialize_udp_filter() {
@@ -317,8 +315,8 @@ mod tests {
}
#[test]
fn request_connection_message() {
let message = r#"{
fn can_deserialize_request_connection_messages() {
let json = r#"{
"ref": null,
"topic": "gateway",
"event": "request_connection",
@@ -368,13 +366,15 @@ mod tests {
]
}
}"#;
// TODO: We are just testing we can deserialize for now.
let _: PhoenixMessage<IngressMessages, ()> = serde_json::from_str(message).unwrap();
let message = serde_json::from_str::<IngressMessages>(json).unwrap();
assert!(matches!(message, IngressMessages::RequestConnection(_)));
}
#[test]
fn request_connection_with_payload() {
let message = r#"{
fn can_deserialize_legacy_request_connection_message() {
let json = r#"{
"event": "request_connection",
"ref": null,
"topic": "gateway",
@@ -460,223 +460,45 @@ mod tests {
"flow_id": "b944e68a-c936-4a81-bd8d-88c45efdcb2c"
}
}"#;
let _: PhoenixMessage<IngressMessages, ()> = serde_json::from_str(message).unwrap();
let message = serde_json::from_str::<IngressMessages>(json).unwrap();
assert!(matches!(message, IngressMessages::RequestConnection(_)));
}
#[test]
fn invalidate_ice_candidates_message() {
let msg = r#"{"event":"invalidate_ice_candidates","ref":null,"topic":"gateway","payload":{"candidates":["candidate:7854631899965427361 1 udp 1694498559 172.28.0.100 47717 typ srflx"],"client_id":"2b1524e6-239e-4570-bc73-70a188e12101"}}"#;
let expected = IngressMessages::InvalidateIceCandidates(ClientIceCandidates {
client_id: "2b1524e6-239e-4570-bc73-70a188e12101".parse().unwrap(),
candidates: vec![
"candidate:7854631899965427361 1 udp 1694498559 172.28.0.100 47717 typ srflx"
.to_owned(),
],
});
fn can_deserialize_invalidate_ice_candidates_message() {
let json = r#"{"event":"invalidate_ice_candidates","ref":null,"topic":"gateway","payload":{"candidates":["candidate:7854631899965427361 1 udp 1694498559 172.28.0.100 47717 typ srflx"],"client_id":"2b1524e6-239e-4570-bc73-70a188e12101"}}"#;
let actual = serde_json::from_str::<IngressMessages>(msg).unwrap();
let message = serde_json::from_str::<IngressMessages>(json).unwrap();
assert_eq!(actual, expected);
assert!(matches!(
message,
IngressMessages::InvalidateIceCandidates(_)
));
}
#[test]
fn init_phoenix_message() {
let m = PhoenixMessage::new_message(
"gateway",
IngressMessages::Init(InitGateway {
interface: Interface {
ipv4: "100.115.164.78".parse().unwrap(),
ipv6: "fd00:2021:1111::2c:f6ab".parse().unwrap(),
upstream_dns: vec![],
},
config: Config {
ipv4_masquerade_enabled: true,
ipv6_masquerade_enabled: true,
},
relays: vec![],
}),
None,
);
fn can_deserialize_init_message() {
let json = r#"{"event":"init","ref":null,"topic":"gateway","payload":{"interface":{"ipv6":"fd00:2021:1111::2c:f6ab","ipv4":"100.115.164.78"},"config":{"ipv4_masquerade_enabled":true,"ipv6_masquerade_enabled":true}}}"#;
let message = r#"{"event":"init","ref":null,"topic":"gateway","payload":{"interface":{"ipv6":"fd00:2021:1111::2c:f6ab","ipv4":"100.115.164.78"},"config":{"ipv4_masquerade_enabled":true,"ipv6_masquerade_enabled":true}}}"#;
let ingress_message =
serde_json::from_str::<PhoenixMessage<IngressMessages, ()>>(message).unwrap();
assert_eq!(m, ingress_message);
let message = serde_json::from_str::<IngressMessages>(json).unwrap();
assert!(matches!(message, IngressMessages::Init(_)));
}
#[test]
fn additional_fields_are_ignore() {
let m = PhoenixMessage::new_message(
"gateway",
IngressMessages::Init(InitGateway {
interface: Interface {
ipv4: "100.115.164.78".parse().unwrap(),
ipv6: "fd00:2021:1111::2c:f6ab".parse().unwrap(),
upstream_dns: vec![],
},
config: Config {
ipv4_masquerade_enabled: true,
ipv6_masquerade_enabled: true,
},
relays: vec![],
}),
None,
);
fn can_deserialize_resource_updated_message() {
let json = r#"{"event":"resource_updated","ref":null,"topic":"gateway","payload":{"id":"57f9ebbb-21d5-4f9f-bf86-b25122fc7a43","name":"?.httpbin","type":"dns","address":"?.httpbin","filters":[{"protocol":"icmp"},{"protocol":"tcp"}]}}"#;
let message = r#"{"event":"init","ref":null,"topic":"gateway","irrelevant":"field","payload":{"more":"info","interface":{"ipv6":"fd00:2021:1111::2c:f6ab","ipv4":"100.115.164.78"},"config":{"ipv4_masquerade_enabled":true,"ipv6_masquerade_enabled":true,"ignored":"field"}}}"#;
let ingress_message =
serde_json::from_str::<PhoenixMessage<IngressMessages, ()>>(message).unwrap();
assert_eq!(m, ingress_message);
let message = serde_json::from_str::<IngressMessages>(json).unwrap();
assert!(matches!(message, IngressMessages::ResourceUpdated(_)));
}
#[test]
fn additional_null_fields_are_ignored() {
let m = PhoenixMessage::new_message(
"gateway",
IngressMessages::Init(InitGateway {
interface: Interface {
ipv4: "100.115.164.78".parse().unwrap(),
ipv6: "fd00:2021:1111::2c:f6ab".parse().unwrap(),
upstream_dns: vec![],
},
config: Config {
ipv4_masquerade_enabled: true,
ipv6_masquerade_enabled: true,
},
relays: vec![],
}),
None,
);
let message = r#"{"event":"init","ref":null,"topic":"gateway","payload":{"additional":null,"interface":{"ipv6":"fd00:2021:1111::2c:f6ab","ipv4":"100.115.164.78"},"config":{"ipv4_masquerade_enabled":true,"ipv6_masquerade_enabled":true}}}"#;
let ingress_message =
serde_json::from_str::<PhoenixMessage<IngressMessages, ()>>(message).unwrap();
assert_eq!(m, ingress_message);
}
#[test]
fn additional_number_fields_are_ignored() {
let m = PhoenixMessage::new_message(
"gateway",
IngressMessages::Init(InitGateway {
interface: Interface {
ipv4: "100.115.164.78".parse().unwrap(),
ipv6: "fd00:2021:1111::2c:f6ab".parse().unwrap(),
upstream_dns: vec![],
},
config: Config {
ipv4_masquerade_enabled: true,
ipv6_masquerade_enabled: true,
},
relays: vec![],
}),
None,
);
let message = r#"{"event":"init","ref":null,"topic":"gateway","payload":{"additional":0.3,"interface":{"ipv6":"fd00:2021:1111::2c:f6ab","ipv4":"100.115.164.78"},"config":{"ipv4_masquerade_enabled":true,"ipv6_masquerade_enabled":true}}}"#;
let ingress_message =
serde_json::from_str::<PhoenixMessage<IngressMessages, ()>>(message).unwrap();
assert_eq!(m, ingress_message);
}
#[test]
fn additional_boolean_fields_are_ignored() {
let m = PhoenixMessage::new_message(
"gateway",
IngressMessages::Init(InitGateway {
interface: Interface {
ipv4: "100.115.164.78".parse().unwrap(),
ipv6: "fd00:2021:1111::2c:f6ab".parse().unwrap(),
upstream_dns: vec![],
},
config: Config {
ipv4_masquerade_enabled: true,
ipv6_masquerade_enabled: true,
},
relays: vec![],
}),
None,
);
let message = r#"{"event":"init","ref":null,"topic":"gateway","payload":{"additional":true,"interface":{"ipv6":"fd00:2021:1111::2c:f6ab","ipv4":"100.115.164.78"},"config":{"ipv4_masquerade_enabled":true,"ipv6_masquerade_enabled":true}}}"#;
let ingress_message =
serde_json::from_str::<PhoenixMessage<IngressMessages, ()>>(message).unwrap();
assert_eq!(m, ingress_message);
}
#[test]
fn additional_object_fields_are_ignored() {
let m = PhoenixMessage::new_message(
"gateway",
IngressMessages::Init(InitGateway {
interface: Interface {
ipv4: "100.115.164.78".parse().unwrap(),
ipv6: "fd00:2021:1111::2c:f6ab".parse().unwrap(),
upstream_dns: vec![],
},
config: Config {
ipv4_masquerade_enabled: true,
ipv6_masquerade_enabled: true,
},
relays: vec![],
}),
None,
);
let message = r#"{"event":"init","ref":null,"topic":"gateway","payload":{"additional":{"ignored":"field"},"interface":{"ipv6":"fd00:2021:1111::2c:f6ab","ipv4":"100.115.164.78"},"config":{"ipv4_masquerade_enabled":true,"ipv6_masquerade_enabled":true}}}"#;
let ingress_message =
serde_json::from_str::<PhoenixMessage<IngressMessages, ()>>(message).unwrap();
assert_eq!(m, ingress_message);
}
#[test]
fn additional_array_fields_are_ignored() {
let m = PhoenixMessage::new_message(
"gateway",
IngressMessages::Init(InitGateway {
interface: Interface {
ipv4: "100.115.164.78".parse().unwrap(),
ipv6: "fd00:2021:1111::2c:f6ab".parse().unwrap(),
upstream_dns: vec![],
},
config: Config {
ipv4_masquerade_enabled: true,
ipv6_masquerade_enabled: true,
},
relays: vec![],
}),
None,
);
let message = r#"{"event":"init","ref":null,"topic":"gateway","payload":{"additional":[true,false],"interface":{"ipv6":"fd00:2021:1111::2c:f6ab","ipv4":"100.115.164.78"},"config":{"ipv4_masquerade_enabled":true,"ipv6_masquerade_enabled":true}}}"#;
let ingress_message =
serde_json::from_str::<PhoenixMessage<IngressMessages, ()>>(message).unwrap();
assert_eq!(m, ingress_message);
}
#[test]
fn resource_updated() {
let message = r#"{"event":"resource_updated","ref":null,"topic":"gateway","payload":{"id":"57f9ebbb-21d5-4f9f-bf86-b25122fc7a43","name":"?.httpbin","type":"dns","address":"?.httpbin","filters":[{"protocol":"icmp"},{"protocol":"tcp"}]}}"#;
let m =
IngressMessages::ResourceUpdated(ResourceDescription::Dns(ResourceDescriptionDns {
id: "57f9ebbb-21d5-4f9f-bf86-b25122fc7a43".parse().unwrap(),
address: "?.httpbin".to_string(),
name: "?.httpbin".to_string(),
filters: vec![
Filter::Icmp,
Filter::Tcp(PortRange {
port_range_end: 65535,
port_range_start: 0,
}),
],
}));
let ingress_message = serde_json::from_str::<IngressMessages>(message).unwrap();
assert_eq!(m, ingress_message);
}
#[test]
fn relays_presence() {
let message = r#"
fn can_deserialize_relays_presence_message() {
let json = r#"
{
"event": "relays_presence",
"ref": null,
@@ -699,22 +521,9 @@ mod tests {
}
}
"#;
let expected = IngressMessages::RelaysPresence(RelaysPresence {
disconnected_ids: vec![
"e95f9517-2152-4677-a16a-fbb2687050a3".parse().unwrap(),
"b0724bd1-a8cc-4faf-88cd-f21159cfec47".parse().unwrap(),
],
connected: vec![Relay::Turn(Turn {
id: "0a133356-7a9e-4b9a-b413-0d95a5720fd8".parse().unwrap(),
expires_at: DateTime::from_timestamp(1719367575, 0).unwrap(),
addr: "172.28.0.101:3478".parse().unwrap(),
username: "1719367575:ZQHcVGkdnfgGmcP1".to_owned(),
password: "ZWYiBeFHOJyYq0mcwAXjRpcuXIJJpzWlOXVdxwttrWg".to_owned(),
})],
});
let ingress_message = serde_json::from_str::<IngressMessages>(message).unwrap();
let message = serde_json::from_str::<IngressMessages>(json).unwrap();
assert_eq!(ingress_message, expected);
assert!(matches!(message, IngressMessages::RelaysPresence(_)));
}
}

View File

@@ -1,7 +1,3 @@
use crate::messages::client::{
ResourceDescription, ResourceDescriptionCidr, ResourceDescriptionDns,
ResourceDescriptionInternet,
};
use connlib_model::{ClientId, GatewayId, RelayId, ResourceId, Site, SiteId};
use ip_network::{IpNetwork, Ipv4Network, Ipv6Network};
use proptest::{
@@ -14,25 +10,23 @@ use std::{
ops::Range,
};
use crate::client::{CidrResource, DnsResource, InternetResource, Resource};
pub fn resource(
sites: impl Strategy<Value = Vec<Site>> + Clone + 'static,
) -> impl Strategy<Value = ResourceDescription> {
) -> impl Strategy<Value = Resource> {
any::<bool>().prop_flat_map(move |is_dns| {
if is_dns {
dns_resource(sites.clone())
.prop_map(ResourceDescription::Dns)
.boxed()
dns_resource(sites.clone()).prop_map(Resource::Dns).boxed()
} else {
cidr_resource(any_ip_network(8), sites.clone())
.prop_map(ResourceDescription::Cidr)
.prop_map(Resource::Cidr)
.boxed()
}
})
}
pub fn dns_resource(
sites: impl Strategy<Value = Vec<Site>>,
) -> impl Strategy<Value = ResourceDescriptionDns> {
pub fn dns_resource(sites: impl Strategy<Value = Vec<Site>>) -> impl Strategy<Value = DnsResource> {
(
resource_id(),
resource_name(),
@@ -40,21 +34,21 @@ pub fn dns_resource(
address_description(),
sites,
)
.prop_map(move |(id, name, address, address_description, sites)| {
ResourceDescriptionDns {
.prop_map(
move |(id, name, address, address_description, sites)| DnsResource {
id,
address,
name,
sites,
address_description,
}
})
},
)
}
pub fn cidr_resource(
ip_network: impl Strategy<Value = IpNetwork>,
sites: impl Strategy<Value = Vec<Site>>,
) -> impl Strategy<Value = ResourceDescriptionCidr> {
) -> impl Strategy<Value = CidrResource> {
(
resource_id(),
resource_name(),
@@ -62,21 +56,21 @@ pub fn cidr_resource(
address_description(),
sites,
)
.prop_map(move |(id, name, address, address_description, sites)| {
ResourceDescriptionCidr {
.prop_map(
move |(id, name, address, address_description, sites)| CidrResource {
id,
address,
name,
sites,
address_description,
}
})
},
)
}
pub fn internet_resource(
sites: impl Strategy<Value = Vec<Site>>,
) -> impl Strategy<Value = ResourceDescriptionInternet> {
(resource_id(), sites).prop_map(move |(id, sites)| ResourceDescriptionInternet {
) -> impl Strategy<Value = InternetResource> {
(resource_id(), sites).prop_map(move |(id, sites)| InternetResource {
name: "Internet Resource".to_string(),
id,
sites,

View File

@@ -2,11 +2,8 @@ use super::{
composite_strategy::CompositeStrategy, sim_client::*, sim_dns::*, sim_gateway::*, sim_net::*,
strategies::*, stub_portal::StubPortal, transition::*,
};
use crate::{client, DomainName, StaticSecret};
use crate::{dns::is_subdomain, proptest::relay_id};
use crate::{
messages::client::{self, ResourceDescription},
DomainName, StaticSecret,
};
use connlib_model::{GatewayId, RelayId, ResourceId};
use domain::base::Rtype;
use proptest::{prelude::*, sample};
@@ -293,7 +290,7 @@ impl ReferenceStateMachine for ReferenceState {
match transition {
Transition::ActivateResource(resource) => {
state.client.exec_mut(|client| match resource {
client::ResourceDescription::Dns(r) => {
client::Resource::Dns(r) => {
client.add_dns_resource(r.clone());
// TODO: PRODUCTION CODE CANNOT DO THIS.
@@ -306,10 +303,8 @@ impl ReferenceStateMachine for ReferenceState {
true
});
}
client::ResourceDescription::Cidr(r) => client.add_cidr_resource(r.clone()),
client::ResourceDescription::Internet(r) => {
client.add_internet_resource(r.clone())
}
client::Resource::Cidr(r) => client.add_cidr_resource(r.clone()),
client::Resource::Internet(r) => client.add_internet_resource(r.clone()),
});
}
Transition::DeactivateResource(id) => {
@@ -659,7 +654,7 @@ impl ReferenceState {
.collect()
}
fn all_resources_not_known_to_client(&self) -> Vec<ResourceDescription> {
fn all_resources_not_known_to_client(&self) -> Vec<client::Resource> {
let mut all_resources = self.portal.all_resources();
all_resources.retain(|r| !self.client.inner().has_resource(r.id()));

View File

@@ -7,13 +7,8 @@ use super::{
IcmpIdentifier, IcmpSeq, QueryId,
};
use crate::{
messages::{
client::{
ResourceDescription, ResourceDescriptionCidr, ResourceDescriptionDns,
ResourceDescriptionInternet,
},
DnsServer, Interface,
},
client::{CidrResource, DnsResource, InternetResource, Resource},
messages::{DnsServer, Interface},
DomainName,
};
use crate::{proptest::*, ClientState};
@@ -287,7 +282,7 @@ pub struct RefClient {
///
/// When reconnecting to the portal, we simulate them being re-added in the same order.
#[derivative(Debug = "ignore")]
resources: Vec<ResourceDescription>,
resources: Vec<Resource>,
#[derivative(Debug = "ignore")]
internet_resource: Option<ResourceId>,
@@ -374,7 +369,7 @@ impl RefClient {
fn recalculate_cidr_routes(&mut self) -> IpNetworkTable<ResourceId> {
let mut table = IpNetworkTable::<ResourceId>::new();
for resource in self.resources.iter().sorted_by_key(|r| r.id()) {
let ResourceDescription::Cidr(resource) = resource else {
let Resource::Cidr(resource) = resource else {
continue;
};
@@ -401,10 +396,9 @@ impl RefClient {
self.connected_gateways.clear();
}
pub(crate) fn add_internet_resource(&mut self, r: ResourceDescriptionInternet) {
pub(crate) fn add_internet_resource(&mut self, r: InternetResource) {
self.internet_resource = Some(r.id);
self.resources
.push(ResourceDescription::Internet(r.clone()));
self.resources.push(Resource::Internet(r.clone()));
if self.disabled_resources.contains(&r.id) {
return;
@@ -414,8 +408,8 @@ impl RefClient {
self.ipv6_routes.insert(r.id, Ipv6Network::DEFAULT_ROUTE);
}
pub(crate) fn add_cidr_resource(&mut self, r: ResourceDescriptionCidr) {
self.resources.push(ResourceDescription::Cidr(r.clone()));
pub(crate) fn add_cidr_resource(&mut self, r: CidrResource) {
self.resources.push(Resource::Cidr(r.clone()));
self.cidr_resources = self.recalculate_cidr_routes();
if self.disabled_resources.contains(&r.id) {
@@ -432,8 +426,8 @@ impl RefClient {
}
}
pub(crate) fn add_dns_resource(&mut self, r: ResourceDescriptionDns) {
self.resources.push(ResourceDescription::Dns(r));
pub(crate) fn add_dns_resource(&mut self, r: DnsResource) {
self.resources.push(Resource::Dns(r));
}
/// Re-adds all resources in the order they have been initially added.
@@ -443,9 +437,9 @@ impl RefClient {
for resource in mem::take(&mut self.resources) {
match resource {
ResourceDescription::Dns(d) => self.add_dns_resource(d),
ResourceDescription::Cidr(c) => self.add_cidr_resource(c),
ResourceDescription::Internet(i) => self.add_internet_resource(i),
Resource::Dns(d) => self.add_dns_resource(d),
Resource::Cidr(c) => self.add_cidr_resource(c),
Resource::Internet(i) => self.add_internet_resource(i),
}
}
}
@@ -838,7 +832,7 @@ impl RefClient {
self.resources.iter().any(|r| r.id() == resource_id)
}
pub(crate) fn all_resources(&self) -> Vec<ResourceDescription> {
pub(crate) fn all_resources(&self) -> Vec<Resource> {
self.resources.clone()
}

View File

@@ -4,15 +4,9 @@ use super::{
sim_relay::ref_relay_host,
stub_portal::StubPortal,
};
use crate::client::{IPV4_RESOURCES, IPV6_RESOURCES};
use crate::client::{CidrResource, DnsResource, InternetResource, IPV4_RESOURCES, IPV6_RESOURCES};
use crate::proptest::*;
use crate::{
messages::{
client::{ResourceDescriptionCidr, ResourceDescriptionDns, ResourceDescriptionInternet},
DnsServer,
},
DomainName,
};
use crate::{messages::DnsServer, DomainName};
use connlib_model::{RelayId, Site};
use ip_network::{IpNetwork, Ipv4Network, Ipv6Network};
use itertools::Itertools;
@@ -152,7 +146,7 @@ fn any_site(sites: BTreeSet<Site>) -> impl Strategy<Value = Site> {
fn cidr_resource_outside_reserved_ranges(
sites: impl Strategy<Value = Site>,
) -> impl Strategy<Value = ResourceDescriptionCidr> {
) -> impl Strategy<Value = CidrResource> {
cidr_resource(any_ip_network(8), sites.prop_map(|s| vec![s]))
.prop_filter(
"tests doesn't support yet CIDR resources overlapping DNS resources",
@@ -169,22 +163,20 @@ fn cidr_resource_outside_reserved_ranges(
.prop_filter("resource must not be in the documentation range because we use those for host addresses and DNS IPs", |r| !r.address.is_documentation())
}
fn internet_resource(
site: impl Strategy<Value = Site>,
) -> impl Strategy<Value = ResourceDescriptionInternet> {
fn internet_resource(site: impl Strategy<Value = Site>) -> impl Strategy<Value = InternetResource> {
crate::proptest::internet_resource(site.prop_map(|s| vec![s]))
}
fn non_wildcard_dns_resource(
site: impl Strategy<Value = Site>,
) -> impl Strategy<Value = ResourceDescriptionDns> {
) -> impl Strategy<Value = DnsResource> {
dns_resource(site.prop_map(|s| vec![s]))
}
fn star_wildcard_dns_resource(
site: impl Strategy<Value = Site>,
) -> impl Strategy<Value = ResourceDescriptionDns> {
dns_resource(site.prop_map(|s| vec![s])).prop_map(|r| ResourceDescriptionDns {
) -> impl Strategy<Value = DnsResource> {
dns_resource(site.prop_map(|s| vec![s])).prop_map(|r| DnsResource {
address: format!("*.{}", r.address),
..r
})
@@ -192,8 +184,8 @@ fn star_wildcard_dns_resource(
fn double_star_wildcard_dns_resource(
site: impl Strategy<Value = Site>,
) -> impl Strategy<Value = ResourceDescriptionDns> {
dns_resource(site.prop_map(|s| vec![s])).prop_map(|r| ResourceDescriptionDns {
) -> impl Strategy<Value = DnsResource> {
dns_resource(site.prop_map(|s| vec![s])).prop_map(|r| DnsResource {
address: format!("**.{}", r.address),
..r
})

View File

@@ -4,9 +4,9 @@ use super::{
sim_net::Host,
strategies::{resolved_ips, subdomain_records},
};
use crate::proptest::*;
use crate::{client, proptest::*};
use crate::{
messages::{client, gateway, DnsServer},
messages::{gateway, DnsServer},
DomainName,
};
use connlib_model::GatewayId;
@@ -31,9 +31,11 @@ pub(crate) struct StubPortal {
#[derivative(Debug = "ignore")]
sites_by_resource: BTreeMap<ResourceId, SiteId>,
cidr_resources: BTreeMap<ResourceId, client::ResourceDescriptionCidr>,
dns_resources: BTreeMap<ResourceId, client::ResourceDescriptionDns>,
internet_resource: client::ResourceDescriptionInternet,
// TODO: Maybe these should use the `messages` types to cover the conversions and to model that that is what we receive from the portal?
cidr_resources: BTreeMap<ResourceId, client::CidrResource>,
dns_resources: BTreeMap<ResourceId, client::DnsResource>,
internet_resource: client::InternetResource,
#[derivative(Debug = "ignore")]
gateway_selector: Selector,
@@ -43,9 +45,9 @@ impl StubPortal {
pub(crate) fn new(
gateways_by_site: BTreeMap<SiteId, BTreeSet<GatewayId>>,
gateway_selector: Selector,
cidr_resources: BTreeSet<client::ResourceDescriptionCidr>,
dns_resources: BTreeSet<client::ResourceDescriptionDns>,
internet_resource: client::ResourceDescriptionInternet,
cidr_resources: BTreeSet<client::CidrResource>,
dns_resources: BTreeSet<client::DnsResource>,
internet_resource: client::InternetResource,
) -> Self {
let cidr_resources = cidr_resources
.into_iter()
@@ -98,18 +100,18 @@ impl StubPortal {
}
}
pub(crate) fn all_resources(&self) -> Vec<client::ResourceDescription> {
pub(crate) fn all_resources(&self) -> Vec<client::Resource> {
self.cidr_resources
.values()
.cloned()
.map(client::ResourceDescription::Cidr)
.map(client::Resource::Cidr)
.chain(
self.dns_resources
.values()
.cloned()
.map(client::ResourceDescription::Dns),
.map(client::Resource::Dns),
)
.chain(iter::once(client::ResourceDescription::Internet(
.chain(iter::once(client::Resource::Internet(
self.internet_resource.clone(),
)))
.collect()

View File

@@ -7,9 +7,9 @@ use super::sim_net::{Host, HostId, RoutingTable};
use super::sim_relay::SimRelay;
use super::stub_portal::StubPortal;
use super::transition::DnsQuery;
use crate::client::Resource;
use crate::dns::is_subdomain;
use crate::gateway::DnsResourceNatEntry;
use crate::messages::client::ResourceDescription;
use crate::tests::assertions::*;
use crate::tests::flux_capacitor::FluxCapacitor;
use crate::tests::transition::Transition;
@@ -121,7 +121,7 @@ impl TunnelTest {
state.client.exec_mut(|c| {
// Flush DNS.
match &resource {
ResourceDescription::Dns(r) => {
Resource::Dns(r) => {
c.dns_records.retain(|domain, _| {
if is_subdomain(domain, &r.address) {
return false;
@@ -130,8 +130,8 @@ impl TunnelTest {
true
});
}
ResourceDescription::Cidr(_) => {}
ResourceDescription::Internet(_) => {}
Resource::Cidr(_) => {}
Resource::Internet(_) => {}
}
c.sut.add_resource(resource);

View File

@@ -1,11 +1,11 @@
use crate::{
client::{IPV4_RESOURCES, IPV6_RESOURCES},
client::{Resource, IPV4_RESOURCES, IPV6_RESOURCES},
proptest::{host_v4, host_v6},
};
use connlib_model::RelayId;
use super::sim_net::{any_ip_stack, any_port, Host};
use crate::messages::{client::ResourceDescription, DnsServer};
use crate::messages::DnsServer;
use connlib_model::{DomainName, ResourceId};
use domain::base::Rtype;
use prop::collection;
@@ -21,7 +21,7 @@ use std::{
#[expect(clippy::large_enum_variant)]
pub(crate) enum Transition {
/// Activate a resource on the client.
ActivateResource(ResourceDescription),
ActivateResource(Resource),
/// Deactivate a resource on the client.
DeactivateResource(ResourceId),
/// Client-side disable resource