webrtc: report the family of the nominated ICE pair

The label a session is reported under names the transport that won the race, not the family
ICE ended up nominating, so a WebRTC session over IPv6 read the same as one over IPv4. Take
it from the remote side of the selected pair - the address the peer is actually reached at,
which the rendezvous-observed address the session is otherwise identified by cannot report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019UzcMTdYTEv2QbMHcTSUy3
This commit is contained in:
rustdesk
2026-08-25 14:45:44 +08:00
parent 7ea29baacf
commit e2aa3832b2
2 changed files with 27 additions and 0 deletions
+13
View File
@@ -122,6 +122,19 @@ impl Stream {
} }
} }
/// Whether an established WebRTC transport reaches the peer over IPv6 (used to name the
/// transport in the UI). `None` for non-WebRTC transports and before ICE selects a pair —
/// every other transport already carries the family in the label it was raced under.
#[inline]
pub async fn webrtc_remote_ipv6(&self) -> Option<bool> {
match self {
#[cfg(feature = "webrtc")]
Stream::WebRTC(s) => s.is_remote_ipv6().await,
#[allow(unreachable_patterns)]
_ => None,
}
}
/// DTLS certificate fingerprint for a WebRTC stream (`local`=true for this endpoint's own /// DTLS certificate fingerprint for a WebRTC stream (`local`=true for this endpoint's own
/// cert, false for the peer's), used to bind the channel to the signed peer identity. /// cert, false for the peer's), used to bind the channel to the signed peer identity.
/// Returns None for non-WebRTC transports, which authenticate via the secretbox key exchange. /// Returns None for non-WebRTC transports, which authenticate via the secretbox key exchange.
+14
View File
@@ -1020,6 +1020,20 @@ impl WebRTCStream {
) )
} }
/// Whether the nominated pair reaches the peer over IPv6 — `None` before one is selected.
/// The remote side on purpose: it is the address the peer is actually reached at, which the
/// rendezvous-observed address the session is otherwise identified by cannot report.
pub async fn is_remote_ipv6(&self) -> Option<bool> {
let dtls = self.pc.sctp().transport();
let pair = dtls.ice_transport().get_selected_candidate_pair().await?;
// Same `Display` parse as `is_relayed`, one token over: a side renders as
// `(remote) <protocol> <typ> <address>:<port>`, and `protocol` is only udp/tcp, so the
// family has to come from the address — an IPv6 literal is the only one with two colons.
let pair = pair.to_string();
let remote = pair.split(" <-> ").nth(1)?;
Some(remote.split_whitespace().nth(3)?.matches(':').count() > 1)
}
#[inline] #[inline]
pub fn take_local_ice_rx(&self) -> Option<mpsc::UnboundedReceiver<String>> { pub fn take_local_ice_rx(&self) -> Option<mpsc::UnboundedReceiver<String>> {
self.local_ice_rx.lock().ok().and_then(|mut rx| rx.take()) self.local_ice_rx.lock().ok().and_then(|mut rx| rx.take())