Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions rtc/src/peer_connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1469,6 +1469,36 @@ where

self.ice_transport_mut()
.set_remote_credentials(remote_ufrag.clone(), remote_pwd.clone())?;

// RFC 8842: on ICE restart the DTLS transport must re-handshake over the new
// ICE path. Extract updated remote fingerprint from the new SDP and reset the
// DTLS endpoint so the next ICESelectedCandidatePairChange triggers a fresh
// handshake.
let (remote_fingerprint, remote_fingerprint_hash) =
extract_fingerprint(parsed_remote_description)?;
let remote_dtls_role = RTCDtlsRole::from(parsed_remote_description);

// Determine local ICE role for DTLS role derivation.
let remote_is_lite = is_lite_set(parsed_remote_description);
let local_ice_role = if (we_offer
&& remote_is_lite == self.setting_engine.candidates.ice_lite)
|| (remote_is_lite && !self.setting_engine.candidates.ice_lite)
{
RTCIceRole::Controlling
} else {
RTCIceRole::Controlled
};

self.dtls_transport_mut().restart(
local_ice_role,
DTLSParameters {
role: remote_dtls_role,
fingerprints: vec![RTCDtlsFingerprint {
algorithm: remote_fingerprint_hash,
value: remote_fingerprint,
}],
},
)?;
}

for candidate in candidates {
Expand Down
81 changes: 69 additions & 12 deletions rtc/src/peer_connection/transport/dtls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,13 @@ impl RTCDtlsTransport {
DEFAULT_DTLS_ROLE_ANSWER
}

pub(crate) fn prepare_transport(
&mut self,
ice_role: RTCIceRole,
remote_dtls_parameters: DTLSParameters,
/// Build a DTLS HandshakeConfig from remote fingerprints.
/// Does not check or change transport state — callable from both initial start and restart.
fn make_handshake_config(
&self,
remote_dtls_parameters: &DTLSParameters,
) -> Result<Arc<::dtls::config::HandshakeConfig>> {
if self.state != RTCDtlsTransportState::New {
return Err(Error::ErrInvalidDTLSStart);
}

self.dtls_role = self.derive_role(ice_role, remote_dtls_parameters.role);

let remote_fingerprints = remote_dtls_parameters.fingerprints;
let remote_fingerprints = remote_dtls_parameters.fingerprints.clone();
let verify_peer_certificate: VerifyPeerCertificateFn = Arc::new(
move |certs: &[Vec<u8>], _chains: &[CertificateDer<'static>]| -> Result<()> {
if certs.is_empty() {
Expand Down Expand Up @@ -153,7 +148,6 @@ impl RTCDtlsTransport {
} else {
return Err(Error::ErrNonCertificate);
};
self.state_change(RTCDtlsTransportState::Connecting);

Ok(Arc::new(
::dtls::config::ConfigBuilder::default()
Expand All @@ -173,6 +167,69 @@ impl RTCDtlsTransport {
))
}

pub(crate) fn prepare_transport(
&mut self,
ice_role: RTCIceRole,
remote_dtls_parameters: DTLSParameters,
) -> Result<Arc<::dtls::config::HandshakeConfig>> {
if self.state != RTCDtlsTransportState::New {
return Err(Error::ErrInvalidDTLSStart);
}

self.dtls_role = self.derive_role(ice_role, remote_dtls_parameters.role);
self.state_change(RTCDtlsTransportState::Connecting);
self.make_handshake_config(&remote_dtls_parameters)
}

/// Re-initialise the DTLS transport for re-handshake after a failed/lost session.
///
/// If DTLS is `Connected`, the existing session is kept alive — it survives ICE
/// restarts because the new ICE path is transparent to DTLS. Only when DTLS is
/// `Failed`, `Closed`, or `Connecting` (handshake was in-flight and lost) is the
/// endpoint replaced so the next `ICESelectedCandidatePairChange` event triggers a
/// fresh handshake. No-ops if state is `New` (initial `start_transports` handles it).
pub(crate) fn restart(
&mut self,
local_ice_role: RTCIceRole,
remote_dtls_parameters: DTLSParameters,
) -> Result<()> {
match self.state {
// Not started yet — initial start_transports handles this path.
RTCDtlsTransportState::New => return Ok(()),
// Session is live; keep it across the ICE restart transparently.
RTCDtlsTransportState::Connected => return Ok(()),
// Failed / Closed / Connecting-but-lost → rebuild and re-handshake.
_ => {}
}

// Derive and update the role (may differ if ICE role swapped during restart).
self.dtls_role = self.derive_role(local_ice_role, remote_dtls_parameters.role);

let dtls_handshake_config = self.make_handshake_config(&remote_dtls_parameters)?;

self.state_change(RTCDtlsTransportState::Connecting);

if self.dtls_role == RTCDtlsRole::Client {
// Client: create a fresh endpoint and store the handshake config so the
// next ICESelectedCandidatePairChange event triggers connect().
self.dtls_endpoint = Some(::dtls::endpoint::Endpoint::new(
TransportContext::default().local_addr,
TransportProtocol::UDP,
None,
));
self.dtls_handshake_config = Some(dtls_handshake_config);
} else {
// Server: create a new accepting endpoint with the updated config.
self.dtls_endpoint = Some(::dtls::endpoint::Endpoint::new(
TransportContext::default().local_addr,
TransportProtocol::UDP,
Some(dtls_handshake_config),
));
}

Ok(())
}

pub(crate) fn role(&self) -> RTCDtlsRole {
self.dtls_role
}
Expand Down