mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
feat(gateway): allow tunneling packets to and from TUN device (#8283)
At present, Clients are only allowed to send packets to resources accessible via the Gateway but not to the Gateway itself. Thus, any application (including Firezone itself) that opens a listening socket on the TUN device will never receive any traffic. This has opens up interesting features like hosting additional services on the machine that the Gateway is running on. Concretely, in order to implement #8221, we will run a DNS server on port 53 of the TUN device as part of the Gateway. The diff for this ended up being a bit larger because we are introducing an `IpConfig` abstraction so we don't have to track 4 IP addresses as separate fields within `ClientOnGateway`; the connection-specific state on a Gateway. This is where we allow / deny traffic from a Client. To allow traffic for this particular Gateway, we need to know our own TUN IP configuration within the component.
This commit is contained in:
@@ -169,8 +169,8 @@ where
|
||||
let dns_servers = config.dns_by_sentinel.left_values().copied().collect();
|
||||
|
||||
self.callbacks.on_set_interface_config(
|
||||
config.ip4,
|
||||
config.ip6,
|
||||
config.ip.v4,
|
||||
config.ip.v6,
|
||||
dns_servers,
|
||||
Vec::from_iter(config.ipv4_routes),
|
||||
Vec::from_iter(config.ipv6_routes),
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::messages::{DnsServer, Interface as InterfaceConfig, IpDnsServer};
|
||||
use crate::messages::{IceCredentials, SecretKey};
|
||||
use crate::peer_store::PeerStore;
|
||||
use crate::unique_packet_buffer::UniquePacketBuffer;
|
||||
use crate::{dns, p2p_control, TunConfig};
|
||||
use crate::{dns, p2p_control, IpConfig, TunConfig};
|
||||
use anyhow::Context;
|
||||
use bimap::BiMap;
|
||||
use connlib_model::{
|
||||
@@ -234,20 +234,15 @@ impl ClientState {
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "proptest"))]
|
||||
pub(crate) fn tunnel_ip4(&self) -> Option<Ipv4Addr> {
|
||||
Some(self.tun_config.as_ref()?.ip4)
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "proptest"))]
|
||||
pub(crate) fn tunnel_ip6(&self) -> Option<Ipv6Addr> {
|
||||
Some(self.tun_config.as_ref()?.ip6)
|
||||
pub(crate) fn tunnel_ip_config(&self) -> Option<crate::IpConfig> {
|
||||
Some(self.tun_config.as_ref()?.ip)
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "proptest"))]
|
||||
pub(crate) fn tunnel_ip_for(&self, dst: IpAddr) -> Option<IpAddr> {
|
||||
Some(match dst {
|
||||
IpAddr::V4(_) => self.tunnel_ip4()?.into(),
|
||||
IpAddr::V6(_) => self.tunnel_ip6()?.into(),
|
||||
IpAddr::V4(_) => self.tunnel_ip_config()?.v4.into(),
|
||||
IpAddr::V6(_) => self.tunnel_ip_config()?.v6.into(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -865,7 +860,7 @@ impl ClientState {
|
||||
};
|
||||
|
||||
self.tcp_dns_client
|
||||
.set_source_interface(tun_config.ip4, tun_config.ip6);
|
||||
.set_source_interface(tun_config.ip.v4, tun_config.ip.v6);
|
||||
|
||||
let upstream_resolvers = self
|
||||
.dns_mapping
|
||||
@@ -991,8 +986,8 @@ impl ClientState {
|
||||
match self.tun_config.as_mut() {
|
||||
Some(existing) => {
|
||||
// We don't really expect these to change but let's update them anyway.
|
||||
existing.ip4 = config.ipv4;
|
||||
existing.ip6 = config.ipv6;
|
||||
existing.ip.v4 = config.ipv4;
|
||||
existing.ip.v6 = config.ipv6;
|
||||
}
|
||||
None => {
|
||||
let (ipv4_routes, ipv6_routes) = self.routes().partition_map(|route| match route {
|
||||
@@ -1000,8 +995,10 @@ impl ClientState {
|
||||
IpNetwork::V6(v6) => itertools::Either::Right(v6),
|
||||
});
|
||||
let new_tun_config = TunConfig {
|
||||
ip4: config.ipv4,
|
||||
ip6: config.ipv6,
|
||||
ip: IpConfig {
|
||||
v4: config.ipv4,
|
||||
v6: config.ipv6,
|
||||
},
|
||||
dns_by_sentinel: Default::default(),
|
||||
ipv4_routes,
|
||||
ipv6_routes,
|
||||
@@ -1596,8 +1593,7 @@ impl ClientState {
|
||||
});
|
||||
|
||||
let new_tun_config = TunConfig {
|
||||
ip4: config.ip4,
|
||||
ip6: config.ip6,
|
||||
ip: config.ip,
|
||||
dns_by_sentinel: dns_mapping
|
||||
.iter()
|
||||
.map(|(sentinel_dns, effective_dns)| (*sentinel_dns, effective_dns.address()))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::messages::gateway::ResourceDescription;
|
||||
use crate::messages::{Answer, IceCredentials, ResolveRequest, SecretKey};
|
||||
use crate::utils::earliest;
|
||||
use crate::{p2p_control, GatewayEvent};
|
||||
use crate::{p2p_control, GatewayEvent, IpConfig};
|
||||
use crate::{peer::ClientOnGateway, peer_store::PeerStore};
|
||||
use anyhow::{Context, Result};
|
||||
use boringtun::x25519::PublicKey;
|
||||
@@ -42,6 +42,8 @@ pub struct GatewayState {
|
||||
/// When to next check whether a resource-access policy has expired.
|
||||
next_expiry_resources_check: Option<Instant>,
|
||||
|
||||
tun_ip_config: Option<IpConfig>,
|
||||
|
||||
buffered_events: VecDeque<GatewayEvent>,
|
||||
buffered_transmits: VecDeque<Transmit<'static>>,
|
||||
}
|
||||
@@ -71,6 +73,7 @@ impl GatewayState {
|
||||
next_expiry_resources_check: Default::default(),
|
||||
buffered_events: VecDeque::default(),
|
||||
buffered_transmits: VecDeque::default(),
|
||||
tun_ip_config: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,8 +230,7 @@ impl GatewayState {
|
||||
preshared_key: SecretKey,
|
||||
client_ice: IceCredentials,
|
||||
gateway_ice: IceCredentials,
|
||||
ipv4: Ipv4Addr,
|
||||
ipv6: Ipv6Addr,
|
||||
client_tun: IpConfig,
|
||||
expires_at: Option<DateTime<Utc>>,
|
||||
resource: ResourceDescription,
|
||||
now: Instant,
|
||||
@@ -248,7 +250,7 @@ impl GatewayState {
|
||||
now,
|
||||
)?;
|
||||
|
||||
let result = self.allow_access(client_id, ipv4, ipv6, expires_at, resource, None);
|
||||
let result = self.allow_access(client_id, client_tun, expires_at, resource, None);
|
||||
debug_assert!(
|
||||
result.is_ok(),
|
||||
"`allow_access` should never fail without a `DnsResourceEntry`"
|
||||
@@ -260,16 +262,17 @@ impl GatewayState {
|
||||
pub fn allow_access(
|
||||
&mut self,
|
||||
client: ClientId,
|
||||
ipv4: Ipv4Addr,
|
||||
ipv6: Ipv6Addr,
|
||||
client_tun: IpConfig,
|
||||
expires_at: Option<DateTime<Utc>>,
|
||||
resource: ResourceDescription,
|
||||
dns_resource_nat: Option<DnsResourceNatEntry>,
|
||||
) -> anyhow::Result<()> {
|
||||
let gateway_tun = self.tun_ip_config.context("TUN device not configured")?;
|
||||
|
||||
let peer = self
|
||||
.peers
|
||||
.entry(client)
|
||||
.or_insert_with(|| ClientOnGateway::new(client, ipv4, ipv6));
|
||||
.or_insert_with(|| ClientOnGateway::new(client, client_tun, gateway_tun));
|
||||
|
||||
peer.add_resource(resource.clone(), expires_at);
|
||||
|
||||
@@ -282,8 +285,8 @@ impl GatewayState {
|
||||
)?;
|
||||
}
|
||||
|
||||
self.peers.add_ip(&client, &ipv4.into());
|
||||
self.peers.add_ip(&client, &ipv6.into());
|
||||
self.peers.add_ip(&client, &client_tun.v4.into());
|
||||
self.peers.add_ip(&client, &client_tun.v6.into());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -428,6 +431,10 @@ impl GatewayState {
|
||||
self.node.update_relays(to_remove, &to_add, now);
|
||||
self.drain_node_events()
|
||||
}
|
||||
|
||||
pub fn update_tun_device(&mut self, config: IpConfig) {
|
||||
self.tun_ip_config = Some(config);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_p2p_control_packet(
|
||||
|
||||
@@ -304,8 +304,7 @@ pub enum ClientEvent {
|
||||
|
||||
#[derive(Clone, derive_more::Debug, PartialEq, Eq)]
|
||||
pub struct TunConfig {
|
||||
pub ip4: Ipv4Addr,
|
||||
pub ip6: Ipv6Addr,
|
||||
pub ip: IpConfig,
|
||||
/// The map of DNS servers that connlib will use.
|
||||
///
|
||||
/// - The "left" values are the connlib-assigned, proxy (or "sentinel") IPs.
|
||||
@@ -320,6 +319,12 @@ pub struct TunConfig {
|
||||
pub ipv6_routes: BTreeSet<Ipv6Network>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct IpConfig {
|
||||
pub v4: Ipv4Addr,
|
||||
pub v6: Ipv6Addr,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GatewayEvent {
|
||||
AddedIceCandidates {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::{hash_map, BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
|
||||
use std::iter;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::net::IpAddr;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::client::{IPV4_RESOURCES, IPV6_RESOURCES};
|
||||
@@ -14,7 +14,7 @@ use ip_network_table::IpNetworkTable;
|
||||
use ip_packet::{IpPacket, Protocol, UnsupportedProtocol};
|
||||
|
||||
use crate::utils::network_contains_network;
|
||||
use crate::GatewayEvent;
|
||||
use crate::{GatewayEvent, IpConfig};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use nat_table::{NatTable, TranslateIncomingResult};
|
||||
@@ -50,8 +50,10 @@ impl GatewayOnClient {
|
||||
/// The state of one client on a gateway.
|
||||
pub struct ClientOnGateway {
|
||||
id: ClientId,
|
||||
ipv4: Ipv4Addr,
|
||||
ipv6: Ipv6Addr,
|
||||
|
||||
client_tun: IpConfig,
|
||||
gateway_tun: IpConfig,
|
||||
|
||||
resources: HashMap<ResourceId, ResourceOnGateway>,
|
||||
/// Caches the existence of internet resource
|
||||
internet_resource_enabled: bool,
|
||||
@@ -62,11 +64,15 @@ pub struct ClientOnGateway {
|
||||
}
|
||||
|
||||
impl ClientOnGateway {
|
||||
pub(crate) fn new(id: ClientId, ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> ClientOnGateway {
|
||||
pub(crate) fn new(
|
||||
id: ClientId,
|
||||
client_tun: IpConfig,
|
||||
gateway_tun: IpConfig,
|
||||
) -> ClientOnGateway {
|
||||
ClientOnGateway {
|
||||
id,
|
||||
ipv4,
|
||||
ipv6,
|
||||
client_tun,
|
||||
gateway_tun,
|
||||
resources: HashMap::new(),
|
||||
filters: IpNetworkTable::new(),
|
||||
permanent_translations: Default::default(),
|
||||
@@ -80,7 +86,10 @@ impl ClientOnGateway {
|
||||
///
|
||||
/// Failure to enforce this would allow one client to send traffic masquarading as a different client.
|
||||
fn allowed_ips(&self) -> [IpAddr; 2] {
|
||||
[IpAddr::from(self.ipv4), IpAddr::from(self.ipv6)]
|
||||
[
|
||||
IpAddr::from(self.client_tun.v4),
|
||||
IpAddr::from(self.client_tun.v6),
|
||||
]
|
||||
}
|
||||
|
||||
/// Setup the NAT for a particular domain within a wildcard DNS resource.
|
||||
@@ -259,7 +268,12 @@ impl ClientOnGateway {
|
||||
.translate_outgoing(&packet, state.resolved_ip, now)?;
|
||||
|
||||
let mut packet = packet
|
||||
.translate_destination(self.ipv4, self.ipv6, source_protocol, real_ip)
|
||||
.translate_destination(
|
||||
self.client_tun.v4,
|
||||
self.client_tun.v6,
|
||||
source_protocol,
|
||||
real_ip,
|
||||
)
|
||||
.context("Failed to translate packet to new destination")?;
|
||||
packet.update_checksum();
|
||||
|
||||
@@ -271,6 +285,13 @@ impl ClientOnGateway {
|
||||
packet: IpPacket,
|
||||
now: Instant,
|
||||
) -> anyhow::Result<Option<IpPacket>> {
|
||||
// Traffic to our own IP is allowed.
|
||||
match packet.destination() {
|
||||
IpAddr::V4(dst) if dst == self.gateway_tun.v4 => return Ok(Some(packet)),
|
||||
IpAddr::V6(dst) if dst == self.gateway_tun.v6 => return Ok(Some(packet)),
|
||||
IpAddr::V4(_) | IpAddr::V6(_) => {}
|
||||
}
|
||||
|
||||
// Filtering a packet is not an error.
|
||||
if let Err(e) = self.ensure_allowed_dst(&packet) {
|
||||
tracing::debug!(filtered_packet = ?packet, "{e:#}");
|
||||
@@ -288,6 +309,13 @@ impl ClientOnGateway {
|
||||
packet: IpPacket,
|
||||
now: Instant,
|
||||
) -> anyhow::Result<Option<IpPacket>> {
|
||||
// Traffic from our own IP is allowed.
|
||||
match packet.source() {
|
||||
IpAddr::V4(src) if src == self.gateway_tun.v4 => return Ok(Some(packet)),
|
||||
IpAddr::V6(src) if src == self.gateway_tun.v6 => return Ok(Some(packet)),
|
||||
IpAddr::V4(_) | IpAddr::V6(_) => {}
|
||||
}
|
||||
|
||||
let Some(packet) = self.transform_tun_to_network(packet, now)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
@@ -316,7 +344,7 @@ impl ClientOnGateway {
|
||||
tracing::debug!(dst = %prototype.outside_dst(), proxy_ip = %prototype.inside_dst(), error = ?prototype.error(), "Destination is unreachable");
|
||||
|
||||
let icmp_error = prototype
|
||||
.into_packet(self.ipv4, self.ipv6)
|
||||
.into_packet(self.client_tun.v4, self.client_tun.v6)
|
||||
.context("Failed to create `DestinationUnreachable` ICMP error")?;
|
||||
|
||||
return Ok(Some(icmp_error));
|
||||
@@ -337,7 +365,7 @@ impl ClientOnGateway {
|
||||
};
|
||||
|
||||
let mut packet = packet
|
||||
.translate_source(self.ipv4, self.ipv6, proto, ip)
|
||||
.translate_source(self.client_tun.v4, self.client_tun.v6, proto, ip)
|
||||
.context("Failed to translate packet to new source")?;
|
||||
packet.update_checksum();
|
||||
|
||||
@@ -590,6 +618,7 @@ mod tests {
|
||||
use crate::{
|
||||
messages::gateway::{Filter, PortRange, ResourceDescription, ResourceDescriptionCidr},
|
||||
peer::nat_table,
|
||||
IpConfig,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use connlib_model::{ClientId, ResourceId};
|
||||
@@ -599,7 +628,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn gateway_filters_expire_individually() {
|
||||
let mut peer = ClientOnGateway::new(client_id(), source_v4_addr(), source_v6_addr());
|
||||
let mut peer = ClientOnGateway::new(client_id(), client_tun(), gateway_tun());
|
||||
let now = Utc::now();
|
||||
let then = now + Duration::from_secs(10);
|
||||
let after_then = then + Duration::from_secs(10);
|
||||
@@ -629,7 +658,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let tcp_packet = ip_packet::make::tcp_packet(
|
||||
source_v4_addr(),
|
||||
client_tun_ipv4(),
|
||||
cidr_v4_resource().hosts().next().unwrap(),
|
||||
5401,
|
||||
80,
|
||||
@@ -638,7 +667,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let udp_packet = ip_packet::make::udp_packet(
|
||||
source_v4_addr(),
|
||||
client_tun_ipv4(),
|
||||
cidr_v4_resource().hosts().next().unwrap(),
|
||||
5401,
|
||||
80,
|
||||
@@ -674,9 +703,41 @@ mod tests {
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allows_packets_for_and_from_gateway_tun_ip() {
|
||||
let mut peer = ClientOnGateway::new(client_id(), client_tun(), gateway_tun());
|
||||
|
||||
let request = ip_packet::make::tcp_packet(
|
||||
client_tun_ipv4(),
|
||||
gateway_tun_ipv4(),
|
||||
5401,
|
||||
80,
|
||||
vec![0; 100],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let response = ip_packet::make::tcp_packet(
|
||||
gateway_tun_ipv4(),
|
||||
client_tun_ipv4(),
|
||||
80,
|
||||
5401,
|
||||
vec![0; 100],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(peer
|
||||
.translate_outbound(request, Instant::now())
|
||||
.unwrap()
|
||||
.is_some());
|
||||
assert!(peer
|
||||
.translate_inbound(response, Instant::now())
|
||||
.unwrap()
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dns_and_cidr_filters_dot_mix() {
|
||||
let mut peer = ClientOnGateway::new(client_id(), source_v4_addr(), source_v6_addr());
|
||||
let mut peer = ClientOnGateway::new(client_id(), client_tun(), gateway_tun());
|
||||
peer.add_resource(foo_dns_resource(), None);
|
||||
peer.add_resource(bar_cidr_resource(), None);
|
||||
peer.setup_nat(
|
||||
@@ -690,7 +751,7 @@ mod tests {
|
||||
assert_eq!(bar_contained_ip(), foo_real_ip());
|
||||
|
||||
let pkt = ip_packet::make::udp_packet(
|
||||
source_v4_addr(),
|
||||
client_tun_ipv4(),
|
||||
bar_contained_ip(),
|
||||
1,
|
||||
bar_allowed_port(),
|
||||
@@ -701,7 +762,7 @@ mod tests {
|
||||
assert!(peer.translate_outbound(pkt, Instant::now()).is_ok());
|
||||
|
||||
let pkt = ip_packet::make::udp_packet(
|
||||
source_v4_addr(),
|
||||
client_tun_ipv4(),
|
||||
bar_contained_ip(),
|
||||
1,
|
||||
foo_allowed_port(),
|
||||
@@ -715,7 +776,7 @@ mod tests {
|
||||
.is_none());
|
||||
|
||||
let pkt = ip_packet::make::udp_packet(
|
||||
source_v4_addr(),
|
||||
client_tun_ipv4(),
|
||||
foo_proxy_ip(),
|
||||
1,
|
||||
bar_allowed_port(),
|
||||
@@ -729,7 +790,7 @@ mod tests {
|
||||
.is_none());
|
||||
|
||||
let pkt = ip_packet::make::udp_packet(
|
||||
source_v4_addr(),
|
||||
client_tun_ipv4(),
|
||||
foo_proxy_ip(),
|
||||
1,
|
||||
foo_allowed_port(),
|
||||
@@ -742,7 +803,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn internet_resource_doesnt_allow_all_traffic_for_dns_resources() {
|
||||
let mut peer = ClientOnGateway::new(client_id(), source_v4_addr(), source_v6_addr());
|
||||
let mut peer = ClientOnGateway::new(client_id(), client_tun(), gateway_tun());
|
||||
peer.add_resource(foo_dns_resource(), None);
|
||||
peer.add_resource(internet_resource(), None);
|
||||
peer.setup_nat(
|
||||
@@ -754,7 +815,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let pkt = ip_packet::make::udp_packet(
|
||||
source_v4_addr(),
|
||||
client_tun_ipv4(),
|
||||
foo_proxy_ip(),
|
||||
1,
|
||||
foo_allowed_port(),
|
||||
@@ -765,7 +826,7 @@ mod tests {
|
||||
assert!(peer.translate_outbound(pkt, Instant::now()).is_ok());
|
||||
|
||||
let pkt = ip_packet::make::udp_packet(
|
||||
source_v4_addr(),
|
||||
client_tun_ipv4(),
|
||||
foo_proxy_ip(),
|
||||
1,
|
||||
600,
|
||||
@@ -779,7 +840,7 @@ mod tests {
|
||||
.is_none());
|
||||
|
||||
let pkt = ip_packet::make::udp_packet(
|
||||
source_v4_addr(),
|
||||
client_tun_ipv4(),
|
||||
"1.1.1.1".parse().unwrap(),
|
||||
1,
|
||||
600,
|
||||
@@ -794,7 +855,7 @@ mod tests {
|
||||
fn dns_resource_packet_is_dropped_after_nat_session_expires() {
|
||||
let _guard = firezone_logging::test("trace");
|
||||
|
||||
let mut peer = ClientOnGateway::new(client_id(), source_v4_addr(), source_v6_addr());
|
||||
let mut peer = ClientOnGateway::new(client_id(), client_tun(), gateway_tun());
|
||||
peer.add_resource(foo_dns_resource(), None);
|
||||
peer.setup_nat(
|
||||
foo_name().parse().unwrap(),
|
||||
@@ -805,7 +866,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let request = ip_packet::make::udp_packet(
|
||||
source_v4_addr(),
|
||||
client_tun_ipv4(),
|
||||
foo_proxy_ip(),
|
||||
1,
|
||||
foo_allowed_port(),
|
||||
@@ -819,7 +880,7 @@ mod tests {
|
||||
|
||||
let response = ip_packet::make::udp_packet(
|
||||
foo_real_ip(),
|
||||
source_v4_addr(),
|
||||
client_tun_ipv4(),
|
||||
foo_allowed_port(),
|
||||
1,
|
||||
vec![0, 0, 0, 0, 0, 0, 0, 0],
|
||||
@@ -836,7 +897,7 @@ mod tests {
|
||||
|
||||
let response = ip_packet::make::udp_packet(
|
||||
foo_real_ip(),
|
||||
source_v4_addr(),
|
||||
client_tun_ipv4(),
|
||||
foo_allowed_port(),
|
||||
1,
|
||||
vec![0, 0, 0, 0, 0, 0, 0, 0],
|
||||
@@ -916,14 +977,36 @@ mod tests {
|
||||
"10.0.0.0/24".parse().unwrap()
|
||||
}
|
||||
|
||||
fn source_v4_addr() -> Ipv4Addr {
|
||||
fn client_tun() -> IpConfig {
|
||||
IpConfig {
|
||||
v4: client_tun_ipv4(),
|
||||
v6: client_tun_ipv6(),
|
||||
}
|
||||
}
|
||||
|
||||
fn client_tun_ipv4() -> Ipv4Addr {
|
||||
"100.64.0.1".parse().unwrap()
|
||||
}
|
||||
|
||||
fn source_v6_addr() -> Ipv6Addr {
|
||||
fn client_tun_ipv6() -> Ipv6Addr {
|
||||
"fd00:2021:1111::1".parse().unwrap()
|
||||
}
|
||||
|
||||
pub fn gateway_tun() -> IpConfig {
|
||||
IpConfig {
|
||||
v4: gateway_tun_ipv4(),
|
||||
v6: gateway_tun_ipv6(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gateway_tun_ipv4() -> Ipv4Addr {
|
||||
"100.64.0.2".parse().unwrap()
|
||||
}
|
||||
|
||||
pub fn gateway_tun_ipv6() -> Ipv6Addr {
|
||||
"fd00:2021:1111::2".parse().unwrap()
|
||||
}
|
||||
|
||||
fn cidr_v4_resource() -> Ipv4Network {
|
||||
"10.0.0.0/24".parse().unwrap()
|
||||
}
|
||||
@@ -943,6 +1026,7 @@ mod tests {
|
||||
|
||||
#[cfg(all(test, feature = "proptest"))]
|
||||
mod proptests {
|
||||
use super::tests::*;
|
||||
use super::*;
|
||||
use crate::messages::gateway::{
|
||||
Filter, PortRange, ResourceDescription, ResourceDescriptionCidr,
|
||||
@@ -957,6 +1041,7 @@ mod proptests {
|
||||
strategy::{Just, Strategy},
|
||||
};
|
||||
use rangemap::RangeInclusiveSet;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::{collections::BTreeSet, ops::RangeInclusive};
|
||||
use test_strategy::Arbitrary;
|
||||
|
||||
@@ -968,22 +1053,29 @@ mod proptests {
|
||||
Protocol,
|
||||
IpAddr,
|
||||
)>,
|
||||
#[strategy(any::<Ipv4Addr>())] src_v4: Ipv4Addr,
|
||||
#[strategy(any::<Ipv6Addr>())] src_v6: Ipv6Addr,
|
||||
#[strategy(any::<Ipv4Addr>())] client_v4: Ipv4Addr,
|
||||
#[strategy(any::<Ipv6Addr>())] client_v6: Ipv6Addr,
|
||||
#[strategy(any::<u16>())] sport: u16,
|
||||
#[strategy(any::<Vec<u8>>())] payload: Vec<u8>,
|
||||
) {
|
||||
// This test could be extended to test multiple src
|
||||
let mut peer = ClientOnGateway::new(client_id, src_v4, src_v6);
|
||||
let mut peer = ClientOnGateway::new(
|
||||
client_id,
|
||||
IpConfig {
|
||||
v4: client_v4,
|
||||
v6: client_v6,
|
||||
},
|
||||
gateway_tun(),
|
||||
);
|
||||
for (resource, _, _) in &resources {
|
||||
peer.add_resource(resource.clone(), None);
|
||||
}
|
||||
|
||||
for (_, protocol, dest) in &resources {
|
||||
let src = if dest.is_ipv4() {
|
||||
src_v4.into()
|
||||
client_v4.into()
|
||||
} else {
|
||||
src_v6.into()
|
||||
client_v6.into()
|
||||
};
|
||||
|
||||
let packet = match protocol {
|
||||
@@ -1002,8 +1094,8 @@ mod proptests {
|
||||
fn gateway_accepts_different_resources_with_same_ip_packet(
|
||||
#[strategy(client_id())] client_id: ClientId,
|
||||
#[strategy(collection::btree_set(resource_id(), 10))] resources_ids: BTreeSet<ResourceId>,
|
||||
#[strategy(any::<Ipv4Addr>())] src_v4: Ipv4Addr,
|
||||
#[strategy(any::<Ipv6Addr>())] src_v6: Ipv6Addr,
|
||||
#[strategy(any::<Ipv4Addr>())] client_v4: Ipv4Addr,
|
||||
#[strategy(any::<Ipv6Addr>())] client_v6: Ipv6Addr,
|
||||
#[strategy(cidr_with_host())] config: (IpNetwork, IpAddr),
|
||||
#[strategy(collection::vec(filters_with_allowed_protocol(), 1..=10))] protocol_config: Vec<
|
||||
(Filters, Protocol),
|
||||
@@ -1013,11 +1105,18 @@ mod proptests {
|
||||
) {
|
||||
let (resource_addr, dest) = config;
|
||||
let src = if dest.is_ipv4() {
|
||||
src_v4.into()
|
||||
client_v4.into()
|
||||
} else {
|
||||
src_v6.into()
|
||||
client_v6.into()
|
||||
};
|
||||
let mut peer = ClientOnGateway::new(client_id, src_v4, src_v6);
|
||||
let mut peer = ClientOnGateway::new(
|
||||
client_id,
|
||||
IpConfig {
|
||||
v4: client_v4,
|
||||
v6: client_v6,
|
||||
},
|
||||
gateway_tun(),
|
||||
);
|
||||
|
||||
for ((filters, _), resource_id) in std::iter::zip(&protocol_config, resources_ids) {
|
||||
// This test could be extended to test multiple src
|
||||
@@ -1050,8 +1149,8 @@ mod proptests {
|
||||
fn gateway_reject_unallowed_packet(
|
||||
#[strategy(client_id())] client_id: ClientId,
|
||||
#[strategy(resource_id())] resource_id: ResourceId,
|
||||
#[strategy(any::<Ipv4Addr>())] src_v4: Ipv4Addr,
|
||||
#[strategy(any::<Ipv6Addr>())] src_v6: Ipv6Addr,
|
||||
#[strategy(any::<Ipv4Addr>())] client_v4: Ipv4Addr,
|
||||
#[strategy(any::<Ipv6Addr>())] client_v6: Ipv6Addr,
|
||||
#[strategy(cidr_with_host())] config: (IpNetwork, IpAddr),
|
||||
#[strategy(filters_with_rejected_protocol())] protocol_config: (Filters, Protocol),
|
||||
#[strategy(any::<u16>())] sport: u16,
|
||||
@@ -1059,13 +1158,20 @@ mod proptests {
|
||||
) {
|
||||
let (resource_addr, dest) = config;
|
||||
let src = if dest.is_ipv4() {
|
||||
src_v4.into()
|
||||
client_v4.into()
|
||||
} else {
|
||||
src_v6.into()
|
||||
client_v6.into()
|
||||
};
|
||||
let (filters, protocol) = protocol_config;
|
||||
// This test could be extended to test multiple src
|
||||
let mut peer = ClientOnGateway::new(client_id, src_v4, src_v6);
|
||||
let mut peer = ClientOnGateway::new(
|
||||
client_id,
|
||||
IpConfig {
|
||||
v4: client_v4,
|
||||
v6: client_v6,
|
||||
},
|
||||
gateway_tun(),
|
||||
);
|
||||
let packet = match protocol {
|
||||
Protocol::Tcp { dport } => tcp_packet(src, dest, sport, dport, payload),
|
||||
Protocol::Udp { dport } => udp_packet(src, dest, sport, dport, payload),
|
||||
@@ -1093,8 +1199,8 @@ mod proptests {
|
||||
#[strategy(client_id())] client_id: ClientId,
|
||||
#[strategy(resource_id())] resource_id_allowed: ResourceId,
|
||||
#[strategy(resource_id())] resource_id_removed: ResourceId,
|
||||
#[strategy(any::<Ipv4Addr>())] src_v4: Ipv4Addr,
|
||||
#[strategy(any::<Ipv6Addr>())] src_v6: Ipv6Addr,
|
||||
#[strategy(any::<Ipv4Addr>())] client_v4: Ipv4Addr,
|
||||
#[strategy(any::<Ipv6Addr>())] client_v6: Ipv6Addr,
|
||||
#[strategy(cidr_with_host())] config: (IpNetwork, IpAddr),
|
||||
#[strategy(non_overlapping_non_empty_filters_with_allowed_protocol())] protocol_config: (
|
||||
(Filters, Protocol),
|
||||
@@ -1105,14 +1211,21 @@ mod proptests {
|
||||
) {
|
||||
let (resource_addr, dest) = config;
|
||||
let src = if dest.is_ipv4() {
|
||||
src_v4.into()
|
||||
client_v4.into()
|
||||
} else {
|
||||
src_v6.into()
|
||||
client_v6.into()
|
||||
};
|
||||
let ((filters_allowed, protocol_allowed), (filters_removed, protocol_removed)) =
|
||||
protocol_config;
|
||||
// This test could be extended to test multiple src
|
||||
let mut peer = ClientOnGateway::new(client_id, src_v4, src_v6);
|
||||
let mut peer = ClientOnGateway::new(
|
||||
client_id,
|
||||
IpConfig {
|
||||
v4: client_v4,
|
||||
v6: client_v6,
|
||||
},
|
||||
gateway_tun(),
|
||||
);
|
||||
|
||||
let packet_allowed = match protocol_allowed {
|
||||
Protocol::Tcp { dport } => tcp_packet(src, dest, sport, dport, payload.clone()),
|
||||
|
||||
@@ -7,7 +7,7 @@ use super::{
|
||||
strategies::latency,
|
||||
unreachable_hosts::{IcmpError, UnreachableHosts},
|
||||
};
|
||||
use crate::GatewayState;
|
||||
use crate::{GatewayState, IpConfig};
|
||||
use anyhow::{bail, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use connlib_model::{GatewayId, RelayId};
|
||||
@@ -16,7 +16,7 @@ use proptest::prelude::*;
|
||||
use snownet::Transmit;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
net::{IpAddr, SocketAddr},
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
@@ -253,6 +253,8 @@ impl SimGateway {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RefGateway {
|
||||
pub(crate) key: PrivateKey,
|
||||
pub(crate) tunnel_ip4: Ipv4Addr,
|
||||
pub(crate) tunnel_ip6: Ipv6Addr,
|
||||
}
|
||||
|
||||
impl RefGateway {
|
||||
@@ -260,21 +262,39 @@ impl RefGateway {
|
||||
///
|
||||
/// This simulates receiving the `init` message from the portal.
|
||||
pub(crate) fn init(self, id: GatewayId, now: Instant) -> SimGateway {
|
||||
SimGateway::new(id, GatewayState::new(self.key.0, now)) // Cheating a bit here by reusing the key as seed.
|
||||
let mut sut = GatewayState::new(self.key.0, now);
|
||||
sut.update_tun_device(IpConfig {
|
||||
v4: self.tunnel_ip4,
|
||||
v6: self.tunnel_ip6,
|
||||
});
|
||||
|
||||
SimGateway::new(id, sut) // Cheating a bit here by reusing the key as seed.
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ref_gateway_host() -> impl Strategy<Value = Host<RefGateway>> {
|
||||
pub(crate) fn ref_gateway_host(
|
||||
tunnel_ip4s: impl Strategy<Value = Ipv4Addr>,
|
||||
tunnel_ip6s: impl Strategy<Value = Ipv6Addr>,
|
||||
) -> impl Strategy<Value = Host<RefGateway>> {
|
||||
host(
|
||||
dual_ip_stack(),
|
||||
any_port(),
|
||||
ref_gateway(),
|
||||
ref_gateway(tunnel_ip4s, tunnel_ip6s),
|
||||
latency(200), // We assume gateways have a somewhat decent Internet connection.
|
||||
)
|
||||
}
|
||||
|
||||
fn ref_gateway() -> impl Strategy<Value = RefGateway> {
|
||||
private_key().prop_map(move |key| RefGateway { key })
|
||||
fn ref_gateway(
|
||||
tunnel_ip4s: impl Strategy<Value = Ipv4Addr>,
|
||||
tunnel_ip6s: impl Strategy<Value = Ipv6Addr>,
|
||||
) -> impl Strategy<Value = RefGateway> {
|
||||
(private_key(), tunnel_ip4s, tunnel_ip6s).prop_map(move |(key, tunnel_ip4, tunnel_ip6)| {
|
||||
RefGateway {
|
||||
key,
|
||||
tunnel_ip4,
|
||||
tunnel_ip6,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn icmp_error_reply(packet: &IpPacket, error: IcmpError) -> Result<IpPacket> {
|
||||
|
||||
@@ -24,7 +24,10 @@ use std::{
|
||||
/// Stub implementation of the portal.
|
||||
#[derive(Clone, derive_more::Debug)]
|
||||
pub(crate) struct StubPortal {
|
||||
gateways_by_site: BTreeMap<SiteId, BTreeSet<GatewayId>>,
|
||||
client_tunnel_ipv4: Ipv4Addr,
|
||||
client_tunnel_ipv6: Ipv6Addr,
|
||||
|
||||
gateways_by_site: BTreeMap<SiteId, BTreeSet<(GatewayId, Ipv4Addr, Ipv6Addr)>>,
|
||||
|
||||
#[debug(skip)]
|
||||
sites_by_resource: BTreeMap<ResourceId, SiteId>,
|
||||
@@ -85,7 +88,32 @@ impl StubPortal {
|
||||
.id,
|
||||
));
|
||||
|
||||
let mut tunnel_ip4s = tunnel_ip4s();
|
||||
let mut tunnel_ip6s = tunnel_ip6s();
|
||||
|
||||
let client_tunnel_ipv4 = tunnel_ip4s.next().unwrap();
|
||||
let client_tunnel_ipv6 = tunnel_ip6s.next().unwrap();
|
||||
|
||||
let gateways_by_site = gateways_by_site
|
||||
.into_iter()
|
||||
.map(|(site, gateways)| {
|
||||
let gateways = gateways
|
||||
.into_iter()
|
||||
.map(|gateway| {
|
||||
let ipv4_addr = tunnel_ip4s.next().unwrap();
|
||||
let ipv6_addr = tunnel_ip6s.next().unwrap();
|
||||
|
||||
(gateway, ipv4_addr, ipv6_addr)
|
||||
})
|
||||
.collect();
|
||||
|
||||
(site, gateways)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
client_tunnel_ipv4,
|
||||
client_tunnel_ipv6,
|
||||
gateways_by_site,
|
||||
gateway_selector,
|
||||
sites_by_resource: BTreeMap::from_iter(
|
||||
@@ -126,7 +154,7 @@ impl StubPortal {
|
||||
.expect("resource to be known");
|
||||
|
||||
let gateways = self.gateways_by_site.get(site_id).unwrap();
|
||||
let gateway = self.gateway_selector.select(gateways);
|
||||
let (gateway, _, _) = self.gateway_selector.select(gateways);
|
||||
|
||||
(*gateway, *site_id)
|
||||
}
|
||||
@@ -182,7 +210,7 @@ impl StubPortal {
|
||||
|
||||
let sid = cidr_site.or(dns_site).or(internet_site)?;
|
||||
let gateways = self.gateways_by_site.get(&sid)?;
|
||||
let gid = self.gateway_selector.try_select(gateways)?;
|
||||
let (gid, _, _) = self.gateway_selector.try_select(gateways)?;
|
||||
|
||||
Some(gid)
|
||||
}
|
||||
@@ -191,7 +219,12 @@ impl StubPortal {
|
||||
self.gateways_by_site
|
||||
.values()
|
||||
.flatten()
|
||||
.map(|gid| (Just(*gid), ref_gateway_host())) // Map each ID to a strategy that samples a gateway.
|
||||
.map(|(gid, ipv4_addr, ipv6_addr)| {
|
||||
(
|
||||
Just(*gid),
|
||||
ref_gateway_host(Just(*ipv4_addr), Just(*ipv6_addr)),
|
||||
)
|
||||
}) // Map each ID to a strategy that samples a gateway.
|
||||
.collect::<Vec<_>>() // A `Vec<Strategy>` implements `Strategy<Value = Vec<_>>`
|
||||
.prop_map(BTreeMap::from_iter)
|
||||
}
|
||||
@@ -201,12 +234,9 @@ impl StubPortal {
|
||||
system_dns: impl Strategy<Value = Vec<IpAddr>>,
|
||||
upstream_dns: impl Strategy<Value = Vec<DnsServer>>,
|
||||
) -> impl Strategy<Value = Host<RefClient>> {
|
||||
let client_tunnel_ip4 = tunnel_ip4s().next().unwrap();
|
||||
let client_tunnel_ip6 = tunnel_ip6s().next().unwrap();
|
||||
|
||||
ref_client_host(
|
||||
Just(client_tunnel_ip4),
|
||||
Just(client_tunnel_ip6),
|
||||
Just(self.client_tunnel_ipv4),
|
||||
Just(self.client_tunnel_ipv6),
|
||||
system_dns,
|
||||
upstream_dns,
|
||||
)
|
||||
|
||||
@@ -233,8 +233,8 @@ impl TunnelTest {
|
||||
Transition::UpdateUpstreamDnsServers(servers) => {
|
||||
state.client.exec_mut(|c| {
|
||||
c.sut.update_interface_config(Interface {
|
||||
ipv4: c.sut.tunnel_ip4().unwrap(),
|
||||
ipv6: c.sut.tunnel_ip6().unwrap(),
|
||||
ipv4: c.sut.tunnel_ip_config().unwrap().v4,
|
||||
ipv6: c.sut.tunnel_ip_config().unwrap().v6,
|
||||
upstream_dns: servers,
|
||||
})
|
||||
});
|
||||
@@ -256,8 +256,8 @@ impl TunnelTest {
|
||||
});
|
||||
}
|
||||
Transition::ReconnectPortal => {
|
||||
let ipv4 = state.client.inner().sut.tunnel_ip4().unwrap();
|
||||
let ipv6 = state.client.inner().sut.tunnel_ip6().unwrap();
|
||||
let ipv4 = state.client.inner().sut.tunnel_ip_config().unwrap().v4;
|
||||
let ipv6 = state.client.inner().sut.tunnel_ip_config().unwrap().v6;
|
||||
let upstream_dns = ref_state.client.inner().upstream_dns_resolvers();
|
||||
let all_resources = ref_state.client.inner().all_resources();
|
||||
|
||||
@@ -715,8 +715,7 @@ impl TunnelTest {
|
||||
preshared_key.clone(),
|
||||
client_ice.clone(),
|
||||
gateway_ice.clone(),
|
||||
self.client.inner().sut.tunnel_ip4().unwrap(),
|
||||
self.client.inner().sut.tunnel_ip6().unwrap(),
|
||||
self.client.inner().sut.tunnel_ip_config().unwrap(),
|
||||
None,
|
||||
resource,
|
||||
now,
|
||||
|
||||
@@ -11,7 +11,7 @@ use firezone_tunnel::messages::gateway::{
|
||||
};
|
||||
use firezone_tunnel::messages::{ConnectionAccepted, GatewayResponse, RelaysPresence};
|
||||
use firezone_tunnel::{
|
||||
DnsResourceNatEntry, GatewayTunnel, ResolveDnsRequest, IPV4_PEERS, IPV6_PEERS,
|
||||
DnsResourceNatEntry, GatewayTunnel, IpConfig, ResolveDnsRequest, IPV4_PEERS, IPV6_PEERS,
|
||||
};
|
||||
use phoenix_channel::{PhoenixChannel, PublicKeyParam};
|
||||
use std::collections::BTreeSet;
|
||||
@@ -235,8 +235,10 @@ impl Eventloop {
|
||||
msg.client.preshared_key,
|
||||
msg.client_ice_credentials,
|
||||
msg.gateway_ice_credentials,
|
||||
msg.client.ipv4,
|
||||
msg.client.ipv6,
|
||||
IpConfig {
|
||||
v4: msg.client.ipv4,
|
||||
v6: msg.client.ipv6,
|
||||
},
|
||||
msg.expires_at,
|
||||
msg.resource,
|
||||
Instant::now(),
|
||||
@@ -354,6 +356,10 @@ impl Eventloop {
|
||||
firezone_tunnel::turn(&init.relays),
|
||||
Instant::now(),
|
||||
);
|
||||
self.tunnel.state_mut().update_tun_device(IpConfig {
|
||||
v4: init.interface.ipv4,
|
||||
v6: init.interface.ipv6,
|
||||
});
|
||||
|
||||
if self
|
||||
.set_interface_tasks
|
||||
@@ -442,8 +448,10 @@ impl Eventloop {
|
||||
|
||||
if let Err(e) = self.tunnel.state_mut().allow_access(
|
||||
req.client.id,
|
||||
req.client.peer.ipv4,
|
||||
req.client.peer.ipv6,
|
||||
IpConfig {
|
||||
v4: req.client.peer.ipv4,
|
||||
v6: req.client.peer.ipv6,
|
||||
},
|
||||
req.expires_at,
|
||||
req.resource,
|
||||
req.client
|
||||
@@ -487,8 +495,10 @@ impl Eventloop {
|
||||
|
||||
if let Err(e) = self.tunnel.state_mut().allow_access(
|
||||
req.client_id,
|
||||
req.client_ipv4,
|
||||
req.client_ipv6,
|
||||
IpConfig {
|
||||
v4: req.client_ipv4,
|
||||
v6: req.client_ipv6,
|
||||
},
|
||||
req.expires_at,
|
||||
req.resource,
|
||||
req.payload.map(|r| DnsResourceNatEntry::new(r, addresses)),
|
||||
|
||||
Reference in New Issue
Block a user