mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
fix(connlib): limit the number of optimistic candidates (#10367)
To facilitate direct connections, `connlib` generates "optimistic" candidates that combine the port of the host candidate with the IP of the server-reflexive candidate. This allows sysadmins to port-forward the Firezone port 52625 on the Gateway, allowing for direct connections to happen behind symmetric NAT. This feature is only really useful for IPv4 as IPv6 doesn't need symmetric NAT due to the larger address space. It is also quite common that users have multiple IPv6 addresses on a single interface. The combination of the two can result in CPU spikes on the Gateway if a client connects and sends over e.g. 10 IPv6 host candidates and various IPv6 server-reflexive candidates. The Gateway then ends up in a loop where it creates an NxM matrix of all these candidates. To mitigate this, we disable optimistic candidates for IPv6 altogether and limit the number of IPv4 optimistic candidates to 2.
This commit is contained in:
@@ -1292,7 +1292,7 @@ fn generate_optimistic_candidates(agent: &mut IceAgent) {
|
||||
|
||||
let optimistic_candidates = public_ips
|
||||
.cartesian_product(host_candidates)
|
||||
.filter(|(ip, base)| ip.is_ipv4() == base.is_ipv4())
|
||||
.filter(|(ip, base)| ip.is_ipv4() && base.is_ipv4())
|
||||
.filter_map(|(ip, base)| {
|
||||
let addr = SocketAddr::new(ip, base.port());
|
||||
|
||||
@@ -1303,6 +1303,7 @@ fn generate_optimistic_candidates(agent: &mut IceAgent) {
|
||||
.ok()
|
||||
})
|
||||
.filter(|c| !remote_candidates.contains(c))
|
||||
.take(2)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for c in optimistic_candidates {
|
||||
@@ -2649,7 +2650,7 @@ impl fmt::Display for SessionId {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddrV4};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -2691,4 +2692,65 @@ mod tests {
|
||||
|
||||
assert!(agent.remote_candidates().contains(&expected_candidate))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skips_optimistic_candidates_for_ipv6() {
|
||||
let base = SocketAddr::V6(SocketAddrV6::new(
|
||||
Ipv6Addr::new(10, 0, 0, 0, 0, 0, 0, 1),
|
||||
52625,
|
||||
0,
|
||||
0,
|
||||
));
|
||||
let addr = IpAddr::V6(Ipv6Addr::new(1, 1, 1, 1, 1, 1, 1, 1));
|
||||
|
||||
let host = Candidate::host(base, "udp").unwrap();
|
||||
let srvflx =
|
||||
Candidate::server_reflexive(SocketAddr::new(addr, 40000), base, "udp").unwrap();
|
||||
|
||||
let mut agent = IceAgent::new();
|
||||
agent.add_remote_candidate(host);
|
||||
agent.add_remote_candidate(srvflx);
|
||||
|
||||
generate_optimistic_candidates(&mut agent);
|
||||
|
||||
let unexpected_candidate =
|
||||
Candidate::server_reflexive(SocketAddr::new(addr, 52625), base, "udp").unwrap();
|
||||
|
||||
assert!(!agent.remote_candidates().contains(&unexpected_candidate))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn limits_optimistic_ipv4_candidates_to_2() {
|
||||
let base = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 52625));
|
||||
let addr1 = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
|
||||
let addr2 = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 2));
|
||||
let addr3 = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 3));
|
||||
|
||||
let host = Candidate::host(base, "udp").unwrap();
|
||||
let srflx1 =
|
||||
Candidate::server_reflexive(SocketAddr::new(addr1, 40000), base, "udp").unwrap();
|
||||
let srflx2 =
|
||||
Candidate::server_reflexive(SocketAddr::new(addr2, 40000), base, "udp").unwrap();
|
||||
let srflx3 =
|
||||
Candidate::server_reflexive(SocketAddr::new(addr3, 40000), base, "udp").unwrap();
|
||||
|
||||
let mut agent = IceAgent::new();
|
||||
agent.add_remote_candidate(host);
|
||||
agent.add_remote_candidate(srflx1);
|
||||
agent.add_remote_candidate(srflx2);
|
||||
agent.add_remote_candidate(srflx3);
|
||||
|
||||
generate_optimistic_candidates(&mut agent);
|
||||
|
||||
let expected_candidate1 =
|
||||
Candidate::server_reflexive(SocketAddr::new(addr1, 52625), base, "udp").unwrap();
|
||||
let expected_candidate2 =
|
||||
Candidate::server_reflexive(SocketAddr::new(addr2, 52625), base, "udp").unwrap();
|
||||
let unexpected_candidate3 =
|
||||
Candidate::server_reflexive(SocketAddr::new(addr3, 52625), base, "udp").unwrap();
|
||||
|
||||
assert!(agent.remote_candidates().contains(&expected_candidate1));
|
||||
assert!(agent.remote_candidates().contains(&expected_candidate2));
|
||||
assert!(!agent.remote_candidates().contains(&unexpected_candidate3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,12 @@ export default function Gateway() {
|
||||
|
||||
return (
|
||||
<Entries downloadLinks={downloadLinks} title="Gateway">
|
||||
<Unreleased></Unreleased>
|
||||
<Unreleased>
|
||||
<ChangeItem pull="10367">
|
||||
Fixes a rare CPU-spike issue in case a Client connected with many
|
||||
possible IPv6 addresses.
|
||||
</ChangeItem>
|
||||
</Unreleased>
|
||||
<Entry version="1.4.16" date={new Date("2025-09-10")}>
|
||||
<ChangeItem pull="10231">
|
||||
Remove the FIREZONE_NUM_TUN_THREADS env variable. The Gateway will now
|
||||
|
||||
Reference in New Issue
Block a user