diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 6519b4324..34695dc55 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2220,6 +2220,7 @@ dependencies = [ "proptest-state-machine", "rand 0.8.5", "rangemap", + "ringbuffer", "secrecy", "serde", "serde_json", @@ -5242,6 +5243,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ringbuffer" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6368f71f205ff9c33c076d170dd56ebf68e8161c733c0caa07a7a5509ed53" + [[package]] name = "rtnetlink" version = "0.14.1" @@ -5900,6 +5907,7 @@ dependencies = [ "itertools 0.13.0", "once_cell", "rand 0.8.5", + "ringbuffer", "secrecy", "sha2", "str0m", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index aa8370141..c26d21a6a 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -74,6 +74,7 @@ native-dialog = "0.7.0" nix = "0.29.0" nu-ansi-term = "0.50" once_cell = "1.17.1" +ringbuffer = "0.15.0" opentelemetry = "0.26.0" opentelemetry-otlp = "0.26.0" opentelemetry_sdk = "0.26.0" diff --git a/rust/connlib/snownet/Cargo.toml b/rust/connlib/snownet/Cargo.toml index f003fb139..3ce758d5f 100644 --- a/rust/connlib/snownet/Cargo.toml +++ b/rust/connlib/snownet/Cargo.toml @@ -16,6 +16,7 @@ ip-packet = { workspace = true } itertools = { workspace = true } once_cell = { workspace = true } rand = { workspace = true } +ringbuffer = { workspace = true } secrecy = { workspace = true } sha2 = { workspace = true } str0m = { workspace = true } diff --git a/rust/connlib/snownet/src/allocation.rs b/rust/connlib/snownet/src/allocation.rs index a44e1db3a..62ade3273 100644 --- a/rust/connlib/snownet/src/allocation.rs +++ b/rust/connlib/snownet/src/allocation.rs @@ -1,7 +1,6 @@ use crate::{ backoff::{self, ExponentialBackoff}, node::{SessionId, Transmit}, - ringbuffer::RingBuffer, utils::earliest, EncryptedPacket, }; @@ -11,6 +10,7 @@ use firezone_logging::{err_with_src, std_dyn_err}; use hex_display::HexDisplayExt as _; use ip_packet::MAX_DATAGRAM_PAYLOAD; use rand::random; +use ringbuffer::{AllocRingBuffer, RingBuffer as _}; use std::{ borrow::Cow, collections::{BTreeMap, VecDeque}, @@ -85,7 +85,7 @@ pub struct Allocation { >, channel_bindings: ChannelBindings, - buffered_channel_bindings: RingBuffer, + buffered_channel_bindings: AllocRingBuffer, last_now: Instant, @@ -229,7 +229,7 @@ impl Allocation { allocation_lifetime: Default::default(), channel_bindings: Default::default(), last_now: now, - buffered_channel_bindings: RingBuffer::new(100), + buffered_channel_bindings: AllocRingBuffer::new(100), software: Software::new(format!("snownet; session={session_id}")) .expect("description has less then 128 chars"), explicit_failure: Default::default(), @@ -549,7 +549,7 @@ impl Allocation { self.log_update(now); - while let Some(peer) = self.buffered_channel_bindings.pop() { + while let Some(peer) = self.buffered_channel_bindings.dequeue() { debug_assert!( self.has_allocation(), "We just received a successful allocation response" diff --git a/rust/connlib/snownet/src/lib.rs b/rust/connlib/snownet/src/lib.rs index d5f46943b..dcb6703c1 100644 --- a/rust/connlib/snownet/src/lib.rs +++ b/rust/connlib/snownet/src/lib.rs @@ -9,7 +9,6 @@ mod candidate_set; mod channel_data; mod index; mod node; -mod ringbuffer; mod stats; mod utils; diff --git a/rust/connlib/snownet/src/node.rs b/rust/connlib/snownet/src/node.rs index f65b6ee8e..89a2160c5 100644 --- a/rust/connlib/snownet/src/node.rs +++ b/rust/connlib/snownet/src/node.rs @@ -1,7 +1,6 @@ use crate::allocation::{self, Allocation, RelaySocket, Socket}; use crate::candidate_set::CandidateSet; use crate::index::IndexLfsr; -use crate::ringbuffer::RingBuffer; use crate::stats::{ConnectionStats, NodeStats}; use crate::utils::earliest; use boringtun::noise::errors::WireGuardError; @@ -17,6 +16,7 @@ use ip_packet::{ use rand::rngs::StdRng; use rand::seq::IteratorRandom; use rand::{random, Rng, SeedableRng}; +use ringbuffer::{AllocRingBuffer, RingBuffer as _}; use secrecy::{ExposeSecret, Secret}; use sha2::Digest; use std::borrow::Cow; @@ -731,7 +731,7 @@ where remote_pub_key: remote, state: ConnectionState::Connecting { relay, - buffered: RingBuffer::new(10), + buffered: AllocRingBuffer::new(128), }, possible_sockets: BTreeSet::default(), span: info_span!(parent: tracing::Span::none(), "connection", %cid), @@ -1638,7 +1638,7 @@ enum ConnectionState { /// A session initiation requires a response that we must not drop, otherwise the connection setup experiences unnecessary delays. /// /// It can also happen if we attempt to encapsulate a packet prior to the WireGuard handshake which triggers the creation of a WireGuard handshake initiation packet. - buffered: RingBuffer>, + buffered: AllocRingBuffer>, }, /// A socket has been nominated. Connected { diff --git a/rust/connlib/snownet/src/ringbuffer.rs b/rust/connlib/snownet/src/ringbuffer.rs deleted file mode 100644 index 585db2110..000000000 --- a/rust/connlib/snownet/src/ringbuffer.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::collections::VecDeque; - -#[derive(Debug)] -pub struct RingBuffer { - buffer: VecDeque, -} - -impl RingBuffer { - pub fn new(capacity: usize) -> Self { - RingBuffer { - buffer: VecDeque::with_capacity(capacity), - } - } - - pub fn push(&mut self, item: T) { - if self.buffer.len() == self.buffer.capacity() { - // Remove the oldest element (at the beginning) if at capacity - self.buffer.remove(0); - } - self.buffer.push_back(item); - } - - pub fn pop(&mut self) -> Option { - self.buffer.pop_front() - } - - pub fn clear(&mut self) { - self.buffer.clear(); - } - - pub fn iter(&self) -> impl Iterator + '_ { - self.buffer.iter() - } - - pub fn into_iter(self) -> impl Iterator { - self.buffer.into_iter() - } - - pub fn len(&self) -> usize { - self.buffer.len() - } - - #[cfg(test)] - fn inner(&self) -> (&[T], &[T]) { - self.buffer.as_slices() - } -} - -impl Extend for RingBuffer -where - T: PartialEq, -{ - fn extend>(&mut self, iter: I) { - for val in iter.into_iter() { - self.push(val) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_push_within_capacity() { - let mut buffer = RingBuffer::new(3); - - buffer.push(1); - buffer.push(2); - buffer.push(3); - - assert_eq!(buffer.inner().0, &[1, 2, 3]); - } - - #[test] - fn test_push_exceeds_capacity() { - let mut buffer = RingBuffer::new(2); - - buffer.push(1); - buffer.push(2); - buffer.push(3); - - assert_eq!(buffer.inner().0, &[2]); - assert_eq!(buffer.inner().1, &[3]); - } -} diff --git a/rust/connlib/tunnel/Cargo.toml b/rust/connlib/tunnel/Cargo.toml index 1fb67e6ae..35d38671d 100644 --- a/rust/connlib/tunnel/Cargo.toml +++ b/rust/connlib/tunnel/Cargo.toml @@ -30,6 +30,7 @@ lru = { workspace = true } proptest = { workspace = true, optional = true } rand = { workspace = true } rangemap = { workspace = true } +ringbuffer = { workspace = true } secrecy = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive", "std"] } serde_json = { workspace = true } diff --git a/rust/connlib/tunnel/proptest-regressions/tests.txt b/rust/connlib/tunnel/proptest-regressions/tests.txt index 348079c33..38ed10ea2 100644 --- a/rust/connlib/tunnel/proptest-regressions/tests.txt +++ b/rust/connlib/tunnel/proptest-regressions/tests.txt @@ -135,3 +135,5 @@ cc 9ee5fca999d50cf96107b1b275a88af1b921f16bd487387a6389552dc1c5b5b4 cc 1a01e04e8dbd861878590647c9d5a6b777a86dd36e299d4519a092f94a928dcd cc d188ee5433064634b07dff90aeb3c26bd3698310bcc4d27f15f35a6296eb8687 cc e60fe97280614a96052e1af1c8d3e4661ec2fead4d22106edf6fb5caf330b6a5 +cc c50c783f60cd7126453f38d2ae37bea3120ebb588c68f99ad5ad4a659f331338 +cc 606dacf44d60870087c7c65fb404f090c52762167e01e534dee1df0557d70304 diff --git a/rust/connlib/tunnel/src/client.rs b/rust/connlib/tunnel/src/client.rs index 0dc009e38..04d56b008 100644 --- a/rust/connlib/tunnel/src/client.rs +++ b/rust/connlib/tunnel/src/client.rs @@ -3,6 +3,7 @@ mod resource; pub(crate) use resource::{CidrResource, Resource}; #[cfg(all(feature = "proptest", test))] pub(crate) use resource::{DnsResource, InternetResource}; +use ringbuffer::{AllocRingBuffer, RingBuffer}; use crate::dns::StubResolver; use crate::messages::{DnsServer, Interface as InterfaceConfig, IpDnsServer}; @@ -151,18 +152,56 @@ pub struct ClientState { } enum DnsResourceNatState { - Pending { sent_at: Instant }, + Pending { + sent_at: Instant, + buffered_packets: AllocRingBuffer, + }, Confirmed, } impl DnsResourceNatState { - fn confirm(&mut self) { - *self = Self::Confirmed; + fn num_buffered_packets(&self) -> usize { + match self { + DnsResourceNatState::Pending { + buffered_packets, .. + } => buffered_packets.len(), + DnsResourceNatState::Confirmed => 0, + } + } + + fn confirm(&mut self) -> impl Iterator { + let buffered_packets = match std::mem::replace(self, DnsResourceNatState::Confirmed) { + DnsResourceNatState::Pending { + buffered_packets, .. + } => Some(buffered_packets.into_iter()), + DnsResourceNatState::Confirmed => None, + }; + + buffered_packets.into_iter().flatten() } } struct PendingFlow { last_intent_sent_at: Instant, + packets: AllocRingBuffer, +} + +impl PendingFlow { + /// How many packets we will at most buffer in a [`PendingFlow`]. + /// + /// `PendingFlow`s are per _resource_ (which could be Internet Resource or wildcard DNS resources). + /// Thus, we may receive a fair few packets before we can send them. + const CAPACITY_POW_2: usize = 7; // 2^7 = 128 + + fn new(now: Instant, packet: IpPacket) -> Self { + let mut packets = AllocRingBuffer::with_capacity_power_of_2(Self::CAPACITY_POW_2); + packets.push(packet); + + Self { + last_intent_sent_at: now, + packets, + } + } } impl ClientState { @@ -285,7 +324,38 @@ impl ClientState { /// 3. If we don't receive a response within 2s and this function is called again, we send another message. /// /// The complexity of this function is O(N) with the number of resolved DNS resources. - fn update_dns_resource_nat(&mut self, now: Instant) { + fn update_dns_resource_nat( + &mut self, + now: Instant, + buffered_packets: impl Iterator, + ) { + // Organise all buffered packets by gateway + domain. + let mut buffered_packets_by_gateway_and_domain = buffered_packets + .map(|packet| { + let (domain, resource) = self + .stub_resolver + .resolve_resource_by_ip(&packet.destination()) + .context("IP is not associated with a DNS resource domain")?; + let gateway_id = self + .resources_gateways + .get(resource) + .context("No gateway for resource")?; + + anyhow::Ok((*gateway_id, domain, packet)) + }) + .filter_map(|res| { + res.inspect_err(|e| tracing::debug!("Dropping buffered packet: {e}")) + .ok() + }) + .fold( + BTreeMap::<_, VecDeque>::new(), + |mut map, (gid, domain, packet)| { + map.entry((gid, domain)).or_default().push_back(packet); + + map + }, + ); + use std::collections::btree_map::Entry; for (domain, rid, proxy_ips, gid) in @@ -305,6 +375,10 @@ impl ClientState { continue; }; + let packets_for_domain = buffered_packets_by_gateway_and_domain + .remove(&(*gid, domain)) + .unwrap_or_default(); + match self .dns_resource_nat_by_gateway .entry((*gid, domain.clone())) @@ -312,13 +386,22 @@ impl ClientState { Entry::Vacant(v) => { self.peers .add_ips_with_resource(gid, proxy_ips.iter().copied(), rid); + let mut buffered_packets = AllocRingBuffer::with_capacity_power_of_2(5); // 2^5 = 32 + buffered_packets.extend(packets_for_domain); - v.insert(DnsResourceNatState::Pending { sent_at: now }); + v.insert(DnsResourceNatState::Pending { + sent_at: now, + buffered_packets, + }); } Entry::Occupied(mut o) => match o.get_mut() { DnsResourceNatState::Confirmed => continue, - DnsResourceNatState::Pending { sent_at } => { + DnsResourceNatState::Pending { + sent_at, + buffered_packets, + } => { let time_since_last_attempt = now.duration_since(*sent_at); + buffered_packets.extend(packets_for_domain); if time_since_last_attempt < Duration::from_secs(2) { continue; @@ -346,18 +429,13 @@ impl ClientState { tracing::debug!(%gid, %domain, "Setting up DNS resource NAT"); - let Some(transmit) = self - .node - .encapsulate(*gid, packet, now) - .inspect_err(|e| tracing::debug!(%gid, "Failed to encapsulate: {e}")) - .ok() - .flatten() - else { - continue; - }; - - self.buffered_transmits - .push_back(transmit.to_transmit().into_owned()); + encapsulate_and_buffer( + packet, + *gid, + now, + &mut self.node, + &mut self.buffered_transmits, + ); } } @@ -435,7 +513,14 @@ impl ClientState { } if let Some(fz_p2p_control) = packet.as_fz_p2p_control() { - handle_p2p_control_packet(gid, fz_p2p_control, &mut self.dns_resource_nat_by_gateway); + handle_p2p_control_packet( + gid, + fz_p2p_control, + &mut self.dns_resource_nat_by_gateway, + &mut self.node, + &mut self.buffered_transmits, + now, + ); return None; } @@ -528,24 +613,24 @@ impl ClientState { let Some(peer) = peer_by_resource_mut(&self.resources_gateways, &mut self.peers, resource) else { - self.on_not_connected_resource(resource, now); + self.on_not_connected_resource(resource, packet, now); return None; }; - // TODO: Don't send packets unless we have a positive response for the DNS resource NAT. - // TODO: Check DNS resource NAT state for the domain that the destination IP belongs to. // Re-send if older than X. - // if let Some((domain, _)) = self.stub_resolver.resolve_resource_by_ip(&dst) { - // if self - // .dns_resource_nat_by_gateway - // .get(&(peer.id(), domain.clone())) - // .is_some_and(|s| s.is_pending()) - // { - // self.update_dns_resource_nat(now); - // } - // } + if let Some((domain, _)) = self.stub_resolver.resolve_resource_by_ip(&dst) { + if let Some(DnsResourceNatState::Pending { + buffered_packets, .. + }) = self + .dns_resource_nat_by_gateway + .get_mut(&(peer.id(), domain.clone())) + { + buffered_packets.push(packet); + return None; + } + } let gid = peer.id(); @@ -620,9 +705,11 @@ impl ClientState { .get(&resource_id) .context("Unknown resource")?; - self.pending_flows + let buffered_packets = self + .pending_flows .remove(&resource_id) - .context("No pending flow for resource")?; + .context("No pending flow for resource")? + .packets; if let Some(old_gateway_id) = self.resources_gateways.insert(resource_id, gateway_id) { if self.peers.get(&old_gateway_id).is_some() { @@ -655,14 +742,27 @@ impl ClientState { self.peers.insert(GatewayOnClient::new(gateway_id), &[]); }; - // This only works for CIDR & Internet Resource. - self.peers.add_ips_with_resource( - &gateway_id, - resource.addresses().into_iter(), - &resource_id, - ); + match resource { + Resource::Cidr(_) | Resource::Internet(_) => { + self.peers.add_ips_with_resource( + &gateway_id, + resource.addresses().into_iter(), + &resource_id, + ); - self.update_dns_resource_nat(now); + // For CIDR and Internet resources, we can directly queue the buffered packets. + for packet in buffered_packets { + encapsulate_and_buffer( + packet, + gateway_id, + now, + &mut self.node, + &mut self.buffered_transmits, + ); + } + } + Resource::Dns(_) => self.update_dns_resource_nat(now, buffered_packets.into_iter()), + } Ok(Ok(())) } @@ -711,17 +811,19 @@ impl ClientState { } #[tracing::instrument(level = "debug", skip_all, fields(%resource))] - fn on_not_connected_resource(&mut self, resource: ResourceId, now: Instant) { + fn on_not_connected_resource(&mut self, resource: ResourceId, packet: IpPacket, now: Instant) { debug_assert!(self.resources_by_id.contains_key(&resource)); match self.pending_flows.entry(resource) { Entry::Vacant(v) => { - v.insert(PendingFlow { - last_intent_sent_at: now, - }); + v.insert(PendingFlow::new(now, packet)); } Entry::Occupied(mut o) => { let pending_flow = o.get_mut(); + pending_flow.packets.push(packet); + let num_buffered = pending_flow.packets.len(); + + tracing::debug!(%num_buffered, "Buffering packet in `PendingFlow`"); let time_since_last_intent = now.duration_since(pending_flow.last_intent_sent_at); @@ -1032,7 +1134,7 @@ impl ClientState { match self.stub_resolver.handle(message) { dns::ResolveStrategy::LocalResponse(response) => { self.clear_dns_resource_nat_for_domain(response.for_slice_ref()); - self.update_dns_resource_nat(now); + self.update_dns_resource_nat(now, iter::empty()); unwrap_or_debug!( self.try_queue_udp_dns_response(upstream, source, &response), @@ -1077,7 +1179,7 @@ impl ClientState { match self.stub_resolver.handle(message.for_slice_ref()) { dns::ResolveStrategy::LocalResponse(response) => { self.clear_dns_resource_nat_for_domain(response.for_slice_ref()); - self.update_dns_resource_nat(now); + self.update_dns_resource_nat(now, iter::empty()); unwrap_or_debug!( self.tcp_dns_server.send_message(query.socket, response), @@ -1512,6 +1614,25 @@ impl ClientState { } } +fn encapsulate_and_buffer( + packet: IpPacket, + gid: GatewayId, + now: Instant, + node: &mut ClientNode, + buffered_transmits: &mut VecDeque>, +) { + let Some(enc_packet) = node + .encapsulate(gid, packet, now) + .inspect_err(|e| tracing::debug!(%gid, "Failed to encapsulate: {e}")) + .ok() + .flatten() + else { + return; + }; + + buffered_transmits.push_back(enc_packet.to_transmit().into_owned()); +} + fn parse_udp_dns_message<'b>(datagram: &UdpSlice<'b>) -> anyhow::Result> { let port = datagram.destination_port(); @@ -1530,6 +1651,9 @@ fn handle_p2p_control_packet( gid: GatewayId, fz_p2p_control: ip_packet::FzP2pControlSlice, dns_resource_nat_by_gateway: &mut BTreeMap<(GatewayId, DomainName), DnsResourceNatState>, + node: &mut ClientNode, + buffered_transmits: &mut VecDeque>, + now: Instant, ) { use p2p_control::dns_resource_nat; @@ -1552,9 +1676,13 @@ fn handle_p2p_control_packet( return; }; - tracing::debug!(%gid, domain = %res.domain, "DNS resource NAT is active"); + tracing::debug!(%gid, domain = %res.domain, num_buffered_packets = %nat_state.num_buffered_packets(), "DNS resource NAT is active"); - nat_state.confirm(); + let buffered_packets = nat_state.confirm(); + + for packet in buffered_packets { + encapsulate_and_buffer(packet, gid, now, node, buffered_transmits); + } } code => { tracing::debug!(code = %code.into_u8(), "Unknown control protocol"); diff --git a/rust/connlib/tunnel/src/tests/reference.rs b/rust/connlib/tunnel/src/tests/reference.rs index e34998cf5..35fdc680d 100644 --- a/rust/connlib/tunnel/src/tests/reference.rs +++ b/rust/connlib/tunnel/src/tests/reference.rs @@ -361,55 +361,9 @@ impl ReferenceState { } }), Transition::SendDnsQueries(queries) => { - let mut new_connections = BTreeSet::new(); - for query in queries { - // Some queries get answered locally. - if state - .client - .inner() - .is_locally_answered_query(&query.domain, query.r_type) - { - tracing::debug!("Expecting locally answered query"); - - state.client.exec_mut(|client| client.on_dns_query(query)); - continue; - } - - // Check if the DNS server is defined as a resource. - let Some(resource) = state.client.inner().dns_query_via_resource(query) else { - // Not a resource, process normally. - state.client.exec_mut(|client| client.on_dns_query(query)); - continue; - }; - - let Some(gateway) = state.portal.gateway_for_resource(resource).copied() else { - tracing::error!("Unknown gateway for resource"); - continue; - }; - - tracing::debug!(%resource, %gateway, "Expecting DNS query via resource"); - - if !state - .client - .inner() - .is_connected_to_internet_or_cidr(resource) - { - tracing::debug!(%resource, %gateway, "Not connected yet, dropping packet"); - - new_connections.insert((resource, gateway)); - - continue; - } - state.client.exec_mut(|client| client.on_dns_query(query)); } - - for (resource, gateway) in new_connections.into_iter() { - state.client.exec_mut(|client| { - client.connect_to_internet_or_cidr_resource(resource, gateway) - }); - } } Transition::SendIcmpPacket { src, diff --git a/rust/connlib/tunnel/src/tests/sim_client.rs b/rust/connlib/tunnel/src/tests/sim_client.rs index c7328a22e..daa5dce14 100644 --- a/rust/connlib/tunnel/src/tests/sim_client.rs +++ b/rust/connlib/tunnel/src/tests/sim_client.rs @@ -431,13 +431,6 @@ pub struct RefClient { #[debug(skip)] pub(crate) connected_cidr_resources: HashSet, - /// The DNS resources the client is connected to. - #[debug(skip)] - pub(crate) connected_dns_resources: HashSet, - - #[debug(skip)] - pub(crate) connected_gateways: BTreeSet, - /// Actively disabled resources by the UI #[debug(skip)] pub(crate) disabled_resources: BTreeSet, @@ -486,7 +479,6 @@ impl RefClient { self.ipv6_routes.remove(resource); self.connected_cidr_resources.remove(resource); - self.connected_dns_resources.remove(resource); if self.internet_resource.is_some_and(|r| &r == resource) { self.connected_internet_resource = false; @@ -530,9 +522,7 @@ impl RefClient { pub(crate) fn reset_connections(&mut self) { self.connected_cidr_resources.clear(); - self.connected_dns_resources.clear(); self.connected_internet_resource = false; - self.connected_gateways.clear(); } pub(crate) fn add_internet_resource(&mut self, r: InternetResource) { @@ -675,41 +665,22 @@ impl RefClient { return; }; - if self.is_connected_to_resource(resource) && self.is_tunnel_ip(src) { - tracing::debug!("Connected to resource, expecting packet to be routed"); - map(self) - .entry(gateway) - .or_default() - .insert(payload, packet_id); + self.connect_to_resource(resource, dst); + + if !self.is_tunnel_ip(src) { return; } - if let Destination::DomainName { name: dst, .. } = &dst { - debug_assert!( - self.dns_records.iter().any(|(name, _)| name == dst), - "Should only sample domains that we resolved" - ); - } - - tracing::debug!("Not connected to resource, expecting to trigger connection intent"); - - self.connect_to_resource(resource, dst, gateway); + map(self) + .entry(gateway) + .or_default() + .insert(payload, packet_id); } - fn connect_to_resource( - &mut self, - resource: ResourceId, - destination: Destination, - gateway: GatewayId, - ) { + fn connect_to_resource(&mut self, resource: ResourceId, destination: Destination) { match destination { - Destination::DomainName { .. } => { - if !self.disabled_resources.contains(&resource) { - self.connected_dns_resources.insert(resource); - self.connected_gateways.insert(gateway); - } - } - Destination::IpAddr(_) => self.connect_to_internet_or_cidr_resource(resource, gateway), + Destination::DomainName { .. } => {} + Destination::IpAddr(_) => self.connect_to_internet_or_cidr_resource(resource), } } @@ -717,20 +688,14 @@ impl RefClient { self.is_connected_to_cidr(resource) || self.is_connected_to_internet(resource) } - pub(crate) fn connect_to_internet_or_cidr_resource( - &mut self, - resource: ResourceId, - gateway: GatewayId, - ) { + pub(crate) fn connect_to_internet_or_cidr_resource(&mut self, resource: ResourceId) { if self.internet_resource.is_some_and(|r| r == resource) { self.connected_internet_resource = true; - self.connected_gateways.insert(gateway); return; } if self.cidr_resources.iter().any(|(_, r)| *r == resource) { self.connected_cidr_resources.insert(resource); - self.connected_gateways.insert(gateway); } } @@ -766,14 +731,6 @@ impl RefClient { .collect_vec() } - fn is_connected_to_resource(&self, resource: ResourceId) -> bool { - if self.is_connected_to_internet_or_cidr(resource) { - return true; - } - - self.connected_dns_resources.contains(&resource) - } - fn is_connected_to_internet(&self, id: ResourceId) -> bool { self.active_internet_resource() == Some(id) && self.connected_internet_resource } @@ -787,21 +744,6 @@ impl RefClient { .filter(|r| !self.disabled_resources.contains(r)) } - pub(crate) fn is_locally_answered_query(&self, domain: &DomainName, rtype: Rtype) -> bool { - let is_known_host = self.known_hosts.contains_key(&domain.to_string()); - let is_dns_resource = self.dns_resource_by_domain(domain).is_some(); - let is_suppported_type = matches!(rtype, Rtype::A | Rtype::AAAA | Rtype::PTR); - - if matches!(rtype, Rtype::PTR) - && crate::dns::reverse_dns_addr(&domain.to_string()) - .is_some_and(|ip| self.known_hosts.values().flatten().any(|c| c == &ip)) - { - return true; - } - - (is_known_host || is_dns_resource) && is_suppported_type - } - fn resource_by_dst(&self, destination: &Destination) -> Option { match destination { Destination::DomainName { name, .. } => { @@ -1097,7 +1039,6 @@ fn ref_client( cidr_resources: IpNetworkTable::new(), dns_records: Default::default(), connected_cidr_resources: Default::default(), - connected_dns_resources: Default::default(), connected_internet_resource: Default::default(), expected_icmp_handshakes: Default::default(), expected_udp_handshakes: Default::default(), @@ -1108,7 +1049,6 @@ fn ref_client( resources: Default::default(), ipv4_routes: Default::default(), ipv6_routes: Default::default(), - connected_gateways: Default::default(), } }, ) diff --git a/website/src/components/Changelog/Android.tsx b/website/src/components/Changelog/Android.tsx index d08db808b..04671cd59 100644 --- a/website/src/components/Changelog/Android.tsx +++ b/website/src/components/Changelog/Android.tsx @@ -24,6 +24,9 @@ export default function Android() { Makes use of the new control protocol, delivering faster and more robust connection establishment. + + Improves connection setup latency by buffering initial packets. + diff --git a/website/src/components/Changelog/Apple.tsx b/website/src/components/Changelog/Apple.tsx index f88ce3176..b0fd07e5f 100644 --- a/website/src/components/Changelog/Apple.tsx +++ b/website/src/components/Changelog/Apple.tsx @@ -24,6 +24,9 @@ export default function Apple() { Makes use of the new control protocol, delivering faster and more robust connection establishment. + + Improves connection setup latency by buffering initial packets. + diff --git a/website/src/components/Changelog/GUI.tsx b/website/src/components/Changelog/GUI.tsx index 160178ec7..344b232dd 100644 --- a/website/src/components/Changelog/GUI.tsx +++ b/website/src/components/Changelog/GUI.tsx @@ -29,6 +29,9 @@ export default function GUI({ title }: { title: string }) { improving performance. )} + + Improves connection setup latency by buffering initial packets. + diff --git a/website/src/components/Changelog/Headless.tsx b/website/src/components/Changelog/Headless.tsx index 8675cb5b7..347d572c1 100644 --- a/website/src/components/Changelog/Headless.tsx +++ b/website/src/components/Changelog/Headless.tsx @@ -27,6 +27,9 @@ export default function Headless() { Uses multiple threads to read & write to the TUN device, greatly improving performance. + + Improves connection setup latency by buffering initial packets. +