fix(connlib): resend candidates on connection upsert (#9986)

Due to network partitions between the Client and the Portal, it is
possible that a Client requests a new connection, then disconnects from
the portal and re-requests the connection once it is reconnected.

On the Gateway, we would have already authorized the first request and
initialise our ICE agents with our local candidates. The second time
around, the connection would be reused. The Client however has lost its
state and therefore, we need to tell it our candidates again.

---------

Signed-off-by: Thomas Eizinger <thomas@eizinger.io>
This commit is contained in:
Thomas Eizinger
2025-07-25 07:01:50 +10:00
committed by GitHub
parent cbe114bddc
commit aebfcd56eb
2 changed files with 28 additions and 7 deletions

View File

@@ -257,9 +257,23 @@ where
.is_some_and(|c| c == &remote_creds)
&& c.tunnel.remote_static_public() == remote
{
tracing::info!(local = ?local_creds, "Reusing existing connection");
c.state.on_upsert(cid, &mut c.agent, now);
tracing::info!(local = ?local_creds, "Reusing existing connection");
for candidate in c.agent.local_candidates() {
signal_candidate_to_remote(cid, candidate, &mut self.pending_events);
}
// Server-reflexive candidates are not in the local candidates of the ICE agent so those need special handling.
for candidate in self
.shared_candidates
.iter()
.filter(|c| c.kind() == CandidateKind::ServerReflexive)
{
signal_candidate_to_remote(cid, candidate, &mut self.pending_events);
}
return Ok(());
}
@@ -1398,12 +1412,7 @@ fn add_local_candidate<TId>(
{
// srflx candidates don't need to be added to the local agent because we always send from the `base` anyway.
if candidate.kind() == CandidateKind::ServerReflexive {
tracing::info!(?candidate, "Signalling candidate to remote");
pending_events.push_back(Event::NewIceCandidate {
connection: id,
candidate: candidate.to_sdp_string(),
});
signal_candidate_to_remote(id, &candidate, pending_events);
return;
}
@@ -1411,6 +1420,14 @@ fn add_local_candidate<TId>(
return;
};
signal_candidate_to_remote(id, candidate, pending_events);
}
fn signal_candidate_to_remote<TId>(
id: TId,
candidate: &Candidate,
pending_events: &mut VecDeque<Event<TId>>,
) {
tracing::info!(?candidate, "Signalling candidate to remote");
pending_events.push_back(Event::NewIceCandidate {

View File

@@ -23,6 +23,10 @@ export default function Gateway() {
return (
<Entries downloadLinks={downloadLinks} title="Gateway">
<Unreleased>
<ChangeItem pull="9986">
Fixes an issue where a Client could not establish a connection unless
their first attempt succeeded.
</ChangeItem>
<ChangeItem pull="9979">
Fixes an issue where connections in low-latency networks (between
Client and Gateway) would fail to establish reliably.