diff --git a/rust/connlib/clients/shared/src/eventloop.rs b/rust/connlib/clients/shared/src/eventloop.rs index 6e06c68f4..e55c474e7 100644 --- a/rust/connlib/clients/shared/src/eventloop.rs +++ b/rust/connlib/clients/shared/src/eventloop.rs @@ -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), diff --git a/rust/connlib/tunnel/src/client.rs b/rust/connlib/tunnel/src/client.rs index 1d1b0537e..15446e012 100644 --- a/rust/connlib/tunnel/src/client.rs +++ b/rust/connlib/tunnel/src/client.rs @@ -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 { - Some(self.tun_config.as_ref()?.ip4) - } - - #[cfg(all(test, feature = "proptest"))] - pub(crate) fn tunnel_ip6(&self) -> Option { - Some(self.tun_config.as_ref()?.ip6) + pub(crate) fn tunnel_ip_config(&self) -> Option { + Some(self.tun_config.as_ref()?.ip) } #[cfg(all(test, feature = "proptest"))] pub(crate) fn tunnel_ip_for(&self, dst: IpAddr) -> Option { 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())) diff --git a/rust/connlib/tunnel/src/gateway.rs b/rust/connlib/tunnel/src/gateway.rs index 78b563f16..821529dae 100644 --- a/rust/connlib/tunnel/src/gateway.rs +++ b/rust/connlib/tunnel/src/gateway.rs @@ -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, + tun_ip_config: Option, + buffered_events: VecDeque, buffered_transmits: VecDeque>, } @@ -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>, 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>, resource: ResourceDescription, dns_resource_nat: Option, ) -> 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( diff --git a/rust/connlib/tunnel/src/lib.rs b/rust/connlib/tunnel/src/lib.rs index 3b182b772..d6352e289 100644 --- a/rust/connlib/tunnel/src/lib.rs +++ b/rust/connlib/tunnel/src/lib.rs @@ -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, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct IpConfig { + pub v4: Ipv4Addr, + pub v6: Ipv6Addr, +} + #[derive(Debug)] pub enum GatewayEvent { AddedIceCandidates { diff --git a/rust/connlib/tunnel/src/peer.rs b/rust/connlib/tunnel/src/peer.rs index d4928b9ce..a4021e6a3 100644 --- a/rust/connlib/tunnel/src/peer.rs +++ b/rust/connlib/tunnel/src/peer.rs @@ -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, /// 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> { + // 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> { + // 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::())] src_v4: Ipv4Addr, - #[strategy(any::())] src_v6: Ipv6Addr, + #[strategy(any::())] client_v4: Ipv4Addr, + #[strategy(any::())] client_v6: Ipv6Addr, #[strategy(any::())] sport: u16, #[strategy(any::>())] payload: Vec, ) { // 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, - #[strategy(any::())] src_v4: Ipv4Addr, - #[strategy(any::())] src_v6: Ipv6Addr, + #[strategy(any::())] client_v4: Ipv4Addr, + #[strategy(any::())] 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::())] src_v4: Ipv4Addr, - #[strategy(any::())] src_v6: Ipv6Addr, + #[strategy(any::())] client_v4: Ipv4Addr, + #[strategy(any::())] client_v6: Ipv6Addr, #[strategy(cidr_with_host())] config: (IpNetwork, IpAddr), #[strategy(filters_with_rejected_protocol())] protocol_config: (Filters, Protocol), #[strategy(any::())] 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::())] src_v4: Ipv4Addr, - #[strategy(any::())] src_v6: Ipv6Addr, + #[strategy(any::())] client_v4: Ipv4Addr, + #[strategy(any::())] 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()), diff --git a/rust/connlib/tunnel/src/tests/sim_gateway.rs b/rust/connlib/tunnel/src/tests/sim_gateway.rs index d77820f2a..34aabd8a5 100644 --- a/rust/connlib/tunnel/src/tests/sim_gateway.rs +++ b/rust/connlib/tunnel/src/tests/sim_gateway.rs @@ -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> { +pub(crate) fn ref_gateway_host( + tunnel_ip4s: impl Strategy, + tunnel_ip6s: impl Strategy, +) -> impl Strategy> { 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 { - private_key().prop_map(move |key| RefGateway { key }) +fn ref_gateway( + tunnel_ip4s: impl Strategy, + tunnel_ip6s: impl Strategy, +) -> impl Strategy { + (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 { diff --git a/rust/connlib/tunnel/src/tests/stub_portal.rs b/rust/connlib/tunnel/src/tests/stub_portal.rs index d4825cd25..2b6f74d65 100644 --- a/rust/connlib/tunnel/src/tests/stub_portal.rs +++ b/rust/connlib/tunnel/src/tests/stub_portal.rs @@ -24,7 +24,10 @@ use std::{ /// Stub implementation of the portal. #[derive(Clone, derive_more::Debug)] pub(crate) struct StubPortal { - gateways_by_site: BTreeMap>, + client_tunnel_ipv4: Ipv4Addr, + client_tunnel_ipv6: Ipv6Addr, + + gateways_by_site: BTreeMap>, #[debug(skip)] sites_by_resource: BTreeMap, @@ -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::>() // A `Vec` implements `Strategy>` .prop_map(BTreeMap::from_iter) } @@ -201,12 +234,9 @@ impl StubPortal { system_dns: impl Strategy>, upstream_dns: impl Strategy>, ) -> impl Strategy> { - 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, ) diff --git a/rust/connlib/tunnel/src/tests/sut.rs b/rust/connlib/tunnel/src/tests/sut.rs index 404b814d7..57ef32485 100644 --- a/rust/connlib/tunnel/src/tests/sut.rs +++ b/rust/connlib/tunnel/src/tests/sut.rs @@ -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, diff --git a/rust/gateway/src/eventloop.rs b/rust/gateway/src/eventloop.rs index 5e9e35d1d..06fa50f2a 100644 --- a/rust/gateway/src/eventloop.rs +++ b/rust/gateway/src/eventloop.rs @@ -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)),