@desklink/host 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -18
- package/bin/desklink-host.mjs +5 -1
- package/dist/bridge.d.ts +7 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +55 -7
- package/dist/bridge.js.map +1 -1
- package/dist/engineProcess.d.ts +9 -2
- package/dist/engineProcess.d.ts.map +1 -1
- package/dist/engineProcess.js +26 -15
- package/dist/engineProcess.js.map +1 -1
- package/dist/protocol.d.ts +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +1 -1
- package/dist/protocol.js.map +1 -1
- package/docs/BOUNDARY.md +4 -1
- package/docs/PROTOCOL.md +20 -5
- package/engine/Cargo.lock +6 -8
- package/engine/Cargo.toml +13 -1
- package/engine/src/clipboard.rs +0 -34
- package/engine/src/convert.rs +0 -170
- package/engine/src/cursor.rs +0 -23
- package/engine/src/input.rs +0 -65
- package/engine/src/input_mac.rs +0 -11
- package/engine/src/keeper.rs +0 -137
- package/engine/src/keymap.rs +0 -107
- package/engine/src/keymap_mac.rs +0 -25
- package/engine/src/mac.rs +0 -28
- package/engine/src/main.rs +3 -2
- package/engine/src/peer.rs +231 -1
- package/engine/src/portal.rs +0 -18
- package/engine/src/protocol.rs +0 -162
- package/engine/src/session.rs +37 -1
- package/engine/src/x11.rs +0 -13
- package/engine/vendor/rtc-ice/Cargo.toml +137 -0
- package/engine/vendor/rtc-ice/DESKLINK-PATCH.md +59 -0
- package/engine/vendor/rtc-ice/LICENSE-APACHE +201 -0
- package/engine/vendor/rtc-ice/LICENSE-MIT +21 -0
- package/engine/vendor/rtc-ice/README.md +30 -0
- package/engine/vendor/rtc-ice/examples/ping_pong.rs +376 -0
- package/engine/vendor/rtc-ice/src/agent/agent_config.rs +126 -0
- package/engine/vendor/rtc-ice/src/agent/agent_proto.rs +172 -0
- package/engine/vendor/rtc-ice/src/agent/agent_selector.rs +566 -0
- package/engine/vendor/rtc-ice/src/agent/agent_stats.rs +292 -0
- package/engine/vendor/rtc-ice/src/agent/agent_test.rs +3388 -0
- package/engine/vendor/rtc-ice/src/agent/mod.rs +1662 -0
- package/engine/vendor/rtc-ice/src/attributes/control/control_test.rs +168 -0
- package/engine/vendor/rtc-ice/src/attributes/control/mod.rs +142 -0
- package/engine/vendor/rtc-ice/src/attributes/mod.rs +6 -0
- package/engine/vendor/rtc-ice/src/attributes/priority/mod.rs +37 -0
- package/engine/vendor/rtc-ice/src/attributes/priority/priority_test.rs +39 -0
- package/engine/vendor/rtc-ice/src/attributes/use_candidate/mod.rs +33 -0
- package/engine/vendor/rtc-ice/src/attributes/use_candidate/use_candidate_test.rs +19 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_host.rs +53 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_pair.rs +215 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_pair_test.rs +201 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_peer_reflexive.rs +49 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_relay.rs +53 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_relay_test.rs +114 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_server_reflexive.rs +53 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_server_reflexive_test.rs +91 -0
- package/engine/vendor/rtc-ice/src/candidate/candidate_test.rs +540 -0
- package/engine/vendor/rtc-ice/src/candidate/mod.rs +679 -0
- package/engine/vendor/rtc-ice/src/lib.rs +85 -0
- package/engine/vendor/rtc-ice/src/mdns/mdns_test.rs +150 -0
- package/engine/vendor/rtc-ice/src/mdns/mod.rs +69 -0
- package/engine/vendor/rtc-ice/src/network_type/mod.rs +162 -0
- package/engine/vendor/rtc-ice/src/network_type/network_type_test.rs +95 -0
- package/engine/vendor/rtc-ice/src/rand/mod.rs +91 -0
- package/engine/vendor/rtc-ice/src/rand/rand_test.rs +115 -0
- package/engine/vendor/rtc-ice/src/state/mod.rs +106 -0
- package/engine/vendor/rtc-ice/src/state/state_test.rs +46 -0
- package/engine/vendor/rtc-ice/src/stats/mod.rs +166 -0
- package/engine/vendor/rtc-ice/src/tcp_type/mod.rs +47 -0
- package/engine/vendor/rtc-ice/src/tcp_type/tcp_type_test.rs +18 -0
- package/engine/vendor/rtc-ice/src/url/mod.rs +266 -0
- package/engine/vendor/rtc-ice/src/url/url_test.rs +142 -0
- package/package.json +8 -3
|
@@ -0,0 +1,1662 @@
|
|
|
1
|
+
//! The Sans-I/O ICE agent.
|
|
2
|
+
//!
|
|
3
|
+
//! An [`Agent`] is given local and remote candidates and inbound datagrams; it produces the
|
|
4
|
+
//! connectivity checks to send, the state transitions to report, and eventually a selected
|
|
5
|
+
//! candidate pair. It owns no sockets and no clock — the caller drives time with
|
|
6
|
+
//! `handle_timeout`.
|
|
7
|
+
//!
|
|
8
|
+
//! Two roles exist: the controlling agent nominates the pair that will carry media, the
|
|
9
|
+
//! controlled agent accepts that choice. Which side controls is decided by which offered, and the
|
|
10
|
+
//! two must not agree — see [`Credentials`] and [`Agent::set_role`].
|
|
11
|
+
#[cfg(test)]
|
|
12
|
+
mod agent_test;
|
|
13
|
+
|
|
14
|
+
/// Configuration for a new [`Agent`]: servers, timeouts and role.
|
|
15
|
+
pub mod agent_config;
|
|
16
|
+
mod agent_proto;
|
|
17
|
+
/// Pair selection and nomination — which candidate pair becomes the selected one.
|
|
18
|
+
pub mod agent_selector;
|
|
19
|
+
/// Snapshot statistics for an agent's candidates and pairs.
|
|
20
|
+
pub mod agent_stats;
|
|
21
|
+
|
|
22
|
+
use agent_config::*;
|
|
23
|
+
use bytes::BytesMut;
|
|
24
|
+
use crypto::{RTCCrypto, RTCCryptoProvider};
|
|
25
|
+
use log::{debug, error, info, trace, warn};
|
|
26
|
+
use mdns::{Mdns, QueryId};
|
|
27
|
+
use sansio::Protocol;
|
|
28
|
+
use std::collections::{HashMap, VecDeque};
|
|
29
|
+
use std::net::{IpAddr, SocketAddr};
|
|
30
|
+
use std::sync::Arc;
|
|
31
|
+
use std::time::{Duration, Instant};
|
|
32
|
+
use stun::attributes::*;
|
|
33
|
+
use stun::fingerprint::*;
|
|
34
|
+
use stun::integrity::*;
|
|
35
|
+
use stun::message::*;
|
|
36
|
+
use stun::textattrs::*;
|
|
37
|
+
use stun::xoraddr::*;
|
|
38
|
+
|
|
39
|
+
use crate::candidate::candidate_peer_reflexive::CandidatePeerReflexiveConfig;
|
|
40
|
+
use crate::candidate::{candidate_pair::*, *};
|
|
41
|
+
use crate::mdns::{MulticastDnsMode, create_multicast_dns, generate_multicast_dns_name};
|
|
42
|
+
use crate::network_type::NetworkType;
|
|
43
|
+
use crate::rand::*;
|
|
44
|
+
use crate::state::*;
|
|
45
|
+
use crate::tcp_type::TcpType;
|
|
46
|
+
use crate::url::*;
|
|
47
|
+
use shared::error::*;
|
|
48
|
+
use shared::{TaggedBytesMut, TransportContext, TransportProtocol};
|
|
49
|
+
|
|
50
|
+
const ZERO_DURATION: Duration = Duration::from_secs(0);
|
|
51
|
+
|
|
52
|
+
#[derive(Debug, Clone)]
|
|
53
|
+
pub(crate) struct BindingRequest {
|
|
54
|
+
pub(crate) timestamp: Instant,
|
|
55
|
+
pub(crate) transaction_id: TransactionId,
|
|
56
|
+
pub(crate) destination: SocketAddr,
|
|
57
|
+
pub(crate) is_use_candidate: bool,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[derive(Default, Clone)]
|
|
61
|
+
/// The ICE credentials for one side of a session, exchanged in SDP.
|
|
62
|
+
pub struct Credentials {
|
|
63
|
+
/// The username fragment, echoed in every STUN check so a receiver can demultiplex.
|
|
64
|
+
pub ufrag: String,
|
|
65
|
+
/// The password, used as the `MESSAGE-INTEGRITY` key.
|
|
66
|
+
pub pwd: String,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#[derive(Default, Clone)]
|
|
70
|
+
pub(crate) struct UfragPwd {
|
|
71
|
+
pub(crate) local_credentials: Credentials,
|
|
72
|
+
pub(crate) remote_credentials: Option<Credentials>,
|
|
73
|
+
/// Credentials generated for an ICE restart that has not been applied yet.
|
|
74
|
+
///
|
|
75
|
+
/// JSEP requires `createOffer` to be free of side effects, so the offer advertises these
|
|
76
|
+
/// while the live session keeps authenticating with `local_credentials`. They are installed
|
|
77
|
+
/// by [`Agent::apply_restart`] when the local description is set. If the offer is discarded,
|
|
78
|
+
/// they are simply dropped and the existing session is undisturbed.
|
|
79
|
+
pub(crate) pending_local_credentials: Option<Credentials>,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn assert_inbound_username(m: &Message, expected_username: &str) -> Result<()> {
|
|
83
|
+
let mut username = Username::new(ATTR_USERNAME, String::new());
|
|
84
|
+
username.get_from(m)?;
|
|
85
|
+
|
|
86
|
+
if username.to_string() != expected_username {
|
|
87
|
+
return Err(Error::Other(format!(
|
|
88
|
+
"{:?} expected({}) actual({})",
|
|
89
|
+
Error::ErrMismatchUsername,
|
|
90
|
+
expected_username,
|
|
91
|
+
username,
|
|
92
|
+
)));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Ok(())
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fn assert_inbound_message_integrity(
|
|
99
|
+
m: &mut Message,
|
|
100
|
+
key: &[u8],
|
|
101
|
+
crypto: &dyn RTCCrypto,
|
|
102
|
+
) -> Result<()> {
|
|
103
|
+
MessageIntegrity::check(m, key, crypto)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// What the agent reports to its caller.
|
|
107
|
+
#[non_exhaustive]
|
|
108
|
+
pub enum Event {
|
|
109
|
+
/// The agent's connection state changed.
|
|
110
|
+
ConnectionStateChange(ConnectionState),
|
|
111
|
+
/// A new pair was selected, carrying the local and remote candidates.
|
|
112
|
+
///
|
|
113
|
+
/// Media should be sent on this pair from now on.
|
|
114
|
+
SelectedCandidatePairChange(Box<Candidate>, Box<Candidate>),
|
|
115
|
+
/// Emitted when the ICE role switches due to a role conflict (RFC 8445 §7.3.1.1).
|
|
116
|
+
/// The bool is `true` if the agent is now controlling, `false` if now controlled.
|
|
117
|
+
RoleChange(bool),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// An [`Event`] together with the instant its condition was observed at.
|
|
121
|
+
///
|
|
122
|
+
/// The agent's events are produced inside `handle_read` / `handle_timeout` / `close`, all of
|
|
123
|
+
/// which know the time, but they are consumed from `poll_event`, which does not. Carrying the
|
|
124
|
+
/// instant in the payload means the consumer is *told* when the condition happened rather than
|
|
125
|
+
/// having to retain an instant of its own and guess — the same channel `TaggedBytesMut` already
|
|
126
|
+
/// provides on the read path.
|
|
127
|
+
pub struct TaggedEvent {
|
|
128
|
+
/// When the condition this event reports was observed.
|
|
129
|
+
pub now: Instant,
|
|
130
|
+
/// The event itself.
|
|
131
|
+
pub event: Event,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Represents the ICE agent.
|
|
135
|
+
pub struct Agent {
|
|
136
|
+
pub(crate) crypto_provider: Arc<dyn RTCCryptoProvider>,
|
|
137
|
+
pub(crate) tie_breaker: u64,
|
|
138
|
+
pub(crate) is_controlling: bool,
|
|
139
|
+
pub(crate) lite: bool,
|
|
140
|
+
|
|
141
|
+
pub(crate) start_time: Instant,
|
|
142
|
+
|
|
143
|
+
pub(crate) connection_state: ConnectionState,
|
|
144
|
+
pub(crate) last_connection_state: ConnectionState,
|
|
145
|
+
|
|
146
|
+
//pub(crate) started_ch_tx: Mutex<Option<broadcast::Sender<()>>>,
|
|
147
|
+
pub(crate) ufrag_pwd: UfragPwd,
|
|
148
|
+
|
|
149
|
+
pub(crate) local_candidates: Vec<Candidate>,
|
|
150
|
+
pub(crate) remote_candidates: Vec<Candidate>,
|
|
151
|
+
pub(crate) candidate_pairs: Vec<CandidatePair>,
|
|
152
|
+
pub(crate) nominated_pair: Option<usize>,
|
|
153
|
+
pub(crate) selected_pair: Option<usize>,
|
|
154
|
+
|
|
155
|
+
// desklink patch: the selected pair media keeps using through an ICE
|
|
156
|
+
// restart, until the restarted generation nominates a new one. Held as
|
|
157
|
+
// owned clones because `apply_restart` clears `candidate_pairs`,
|
|
158
|
+
// `local_candidates` and `remote_candidates`, so the old indexes dangle.
|
|
159
|
+
// See DESKLINK-PATCH.md.
|
|
160
|
+
pub(crate) restart_media_pair: Option<(Candidate, Candidate)>,
|
|
161
|
+
|
|
162
|
+
// LRU of outbound Binding request Transaction IDs
|
|
163
|
+
pub(crate) pending_binding_requests: Vec<BindingRequest>,
|
|
164
|
+
|
|
165
|
+
// the following variables won't be changed after init_with_defaults()
|
|
166
|
+
pub(crate) insecure_skip_verify: bool,
|
|
167
|
+
pub(crate) max_binding_requests: u16,
|
|
168
|
+
pub(crate) host_acceptance_min_wait: Duration,
|
|
169
|
+
pub(crate) srflx_acceptance_min_wait: Duration,
|
|
170
|
+
pub(crate) prflx_acceptance_min_wait: Duration,
|
|
171
|
+
pub(crate) relay_acceptance_min_wait: Duration,
|
|
172
|
+
// How long connectivity checks can fail before the ICE Agent
|
|
173
|
+
// goes to disconnected
|
|
174
|
+
pub(crate) disconnected_timeout: Duration,
|
|
175
|
+
// How long connectivity checks can fail before the ICE Agent
|
|
176
|
+
// goes to failed
|
|
177
|
+
pub(crate) failed_timeout: Duration,
|
|
178
|
+
// How often should we send keepalive packets?
|
|
179
|
+
// 0 means never
|
|
180
|
+
pub(crate) keepalive_interval: Duration,
|
|
181
|
+
// When the last STUN consent ping was sent.
|
|
182
|
+
pub(crate) last_consent_sent: Instant,
|
|
183
|
+
// How often should we run our internal taskLoop to check for state changes when connecting
|
|
184
|
+
pub(crate) check_interval: Duration,
|
|
185
|
+
pub(crate) checking_duration: Instant,
|
|
186
|
+
pub(crate) last_checking_time: Instant,
|
|
187
|
+
// When set, a connectivity check has been requested and must be run on the next
|
|
188
|
+
// `handle_timeout`. Checks are deferred (rather than run inline) so that adding a
|
|
189
|
+
// candidate can never re-enter `contact()` and mutate agent state - e.g. transition
|
|
190
|
+
// to `Failed` and drop candidates - while a caller still holds indices into the
|
|
191
|
+
// candidate vectors. See issue #88.
|
|
192
|
+
pub(crate) force_candidate_contact: bool,
|
|
193
|
+
|
|
194
|
+
pub(crate) mdns: Option<Mdns>,
|
|
195
|
+
pub(crate) mdns_queries: HashMap<QueryId, Candidate>,
|
|
196
|
+
|
|
197
|
+
pub(crate) mdns_mode: MulticastDnsMode,
|
|
198
|
+
pub(crate) mdns_local_name: String,
|
|
199
|
+
pub(crate) mdns_local_ip: Option<IpAddr>,
|
|
200
|
+
|
|
201
|
+
pub(crate) candidate_types: Vec<CandidateType>,
|
|
202
|
+
pub(crate) network_types: Vec<NetworkType>,
|
|
203
|
+
pub(crate) urls: Vec<Url>,
|
|
204
|
+
|
|
205
|
+
pub(crate) write_outs: VecDeque<TaggedBytesMut>,
|
|
206
|
+
pub(crate) event_outs: VecDeque<TaggedEvent>,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
impl Agent {
|
|
210
|
+
/// Creates a new Agent.
|
|
211
|
+
///
|
|
212
|
+
/// The crypto provider is supplied by the caller; this crate never resolves a default.
|
|
213
|
+
/// Builds an agent whose clock starts at `now`.
|
|
214
|
+
///
|
|
215
|
+
/// `now` is a constructor argument rather than an `AgentConfig` field because it is a value
|
|
216
|
+
/// consumed once at construction, not a setting that persists for the agent's lifetime — and
|
|
217
|
+
/// because `Instant` has no `Default`, so a config field would force `AgentConfig` to drop
|
|
218
|
+
/// `#[derive(Default)]` and break every `..Default::default()` in its 31 call sites.
|
|
219
|
+
pub fn new(
|
|
220
|
+
now: Instant,
|
|
221
|
+
config: Arc<AgentConfig>,
|
|
222
|
+
crypto_provider: Arc<dyn RTCCryptoProvider>,
|
|
223
|
+
) -> Result<Self> {
|
|
224
|
+
let tie_breaker = generate_tie_breaker(crypto_provider.random())?;
|
|
225
|
+
|
|
226
|
+
let mut mdns_local_name = config.multicast_dns_local_name.clone();
|
|
227
|
+
if mdns_local_name.is_empty() {
|
|
228
|
+
mdns_local_name = generate_multicast_dns_name();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if !mdns_local_name.ends_with(".local") || mdns_local_name.split('.').count() != 2 {
|
|
232
|
+
return Err(Error::ErrInvalidMulticastDnshostName);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let mdns_mode = config.multicast_dns_mode;
|
|
236
|
+
let mdns = create_multicast_dns(
|
|
237
|
+
now,
|
|
238
|
+
mdns_mode,
|
|
239
|
+
&mdns_local_name,
|
|
240
|
+
&config.multicast_dns_local_ip,
|
|
241
|
+
&config.multicast_dns_query_timeout,
|
|
242
|
+
)
|
|
243
|
+
.unwrap_or_else(|err| {
|
|
244
|
+
// Opportunistic mDNS: If we can't open the connection, that's ok: we
|
|
245
|
+
// can continue without it.
|
|
246
|
+
warn!("Failed to initialize mDNS {mdns_local_name}: {err}");
|
|
247
|
+
None
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
let candidate_types = if config.candidate_types.is_empty() {
|
|
251
|
+
default_candidate_types()
|
|
252
|
+
} else {
|
|
253
|
+
config.candidate_types.clone()
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
if config.lite && (candidate_types.len() != 1 || candidate_types[0] != CandidateType::Host)
|
|
257
|
+
{
|
|
258
|
+
return Err(Error::ErrLiteUsingNonHostCandidates);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if !config.urls.is_empty()
|
|
262
|
+
&& !contains_candidate_type(CandidateType::ServerReflexive, &candidate_types)
|
|
263
|
+
&& !contains_candidate_type(CandidateType::Relay, &candidate_types)
|
|
264
|
+
{
|
|
265
|
+
return Err(Error::ErrUselessUrlsProvided);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let mut agent = Self {
|
|
269
|
+
crypto_provider,
|
|
270
|
+
tie_breaker,
|
|
271
|
+
is_controlling: config.is_controlling,
|
|
272
|
+
lite: config.lite,
|
|
273
|
+
|
|
274
|
+
start_time: now,
|
|
275
|
+
|
|
276
|
+
nominated_pair: None,
|
|
277
|
+
selected_pair: None,
|
|
278
|
+
// desklink patch: nothing is preserved across a restart yet.
|
|
279
|
+
restart_media_pair: None,
|
|
280
|
+
candidate_pairs: vec![],
|
|
281
|
+
|
|
282
|
+
connection_state: ConnectionState::New,
|
|
283
|
+
|
|
284
|
+
insecure_skip_verify: config.insecure_skip_verify,
|
|
285
|
+
|
|
286
|
+
//started_ch_tx: MuteSome(started_ch_tx)),
|
|
287
|
+
|
|
288
|
+
//won't change after init_with_defaults()
|
|
289
|
+
max_binding_requests: if let Some(max_binding_requests) = config.max_binding_requests {
|
|
290
|
+
max_binding_requests
|
|
291
|
+
} else {
|
|
292
|
+
DEFAULT_MAX_BINDING_REQUESTS
|
|
293
|
+
},
|
|
294
|
+
host_acceptance_min_wait: if let Some(host_acceptance_min_wait) =
|
|
295
|
+
config.host_acceptance_min_wait
|
|
296
|
+
{
|
|
297
|
+
host_acceptance_min_wait
|
|
298
|
+
} else {
|
|
299
|
+
DEFAULT_HOST_ACCEPTANCE_MIN_WAIT
|
|
300
|
+
},
|
|
301
|
+
srflx_acceptance_min_wait: if let Some(srflx_acceptance_min_wait) =
|
|
302
|
+
config.srflx_acceptance_min_wait
|
|
303
|
+
{
|
|
304
|
+
srflx_acceptance_min_wait
|
|
305
|
+
} else {
|
|
306
|
+
DEFAULT_SRFLX_ACCEPTANCE_MIN_WAIT
|
|
307
|
+
},
|
|
308
|
+
prflx_acceptance_min_wait: if let Some(prflx_acceptance_min_wait) =
|
|
309
|
+
config.prflx_acceptance_min_wait
|
|
310
|
+
{
|
|
311
|
+
prflx_acceptance_min_wait
|
|
312
|
+
} else {
|
|
313
|
+
DEFAULT_PRFLX_ACCEPTANCE_MIN_WAIT
|
|
314
|
+
},
|
|
315
|
+
relay_acceptance_min_wait: if let Some(relay_acceptance_min_wait) =
|
|
316
|
+
config.relay_acceptance_min_wait
|
|
317
|
+
{
|
|
318
|
+
relay_acceptance_min_wait
|
|
319
|
+
} else {
|
|
320
|
+
DEFAULT_RELAY_ACCEPTANCE_MIN_WAIT
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
// How long connectivity checks can fail before the ICE Agent
|
|
324
|
+
// goes to disconnected
|
|
325
|
+
disconnected_timeout: if let Some(disconnected_timeout) = config.disconnected_timeout {
|
|
326
|
+
disconnected_timeout
|
|
327
|
+
} else {
|
|
328
|
+
DEFAULT_DISCONNECTED_TIMEOUT
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
// How long connectivity checks can fail before the ICE Agent
|
|
332
|
+
// goes to failed
|
|
333
|
+
failed_timeout: if let Some(failed_timeout) = config.failed_timeout {
|
|
334
|
+
failed_timeout
|
|
335
|
+
} else {
|
|
336
|
+
DEFAULT_FAILED_TIMEOUT
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
// How often should we send keepalive packets?
|
|
340
|
+
// 0 means never
|
|
341
|
+
keepalive_interval: if let Some(keepalive_interval) = config.keepalive_interval {
|
|
342
|
+
keepalive_interval
|
|
343
|
+
} else {
|
|
344
|
+
DEFAULT_KEEPALIVE_INTERVAL
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
// How often should we run our internal taskLoop to check for state changes when connecting
|
|
348
|
+
check_interval: if config.check_interval == Duration::from_secs(0) {
|
|
349
|
+
DEFAULT_CHECK_INTERVAL
|
|
350
|
+
} else {
|
|
351
|
+
config.check_interval
|
|
352
|
+
},
|
|
353
|
+
last_consent_sent: now,
|
|
354
|
+
checking_duration: now,
|
|
355
|
+
last_checking_time: now,
|
|
356
|
+
force_candidate_contact: false,
|
|
357
|
+
last_connection_state: ConnectionState::Unspecified,
|
|
358
|
+
|
|
359
|
+
mdns,
|
|
360
|
+
mdns_queries: HashMap::new(),
|
|
361
|
+
|
|
362
|
+
mdns_mode,
|
|
363
|
+
mdns_local_name,
|
|
364
|
+
mdns_local_ip: config.multicast_dns_local_ip,
|
|
365
|
+
|
|
366
|
+
ufrag_pwd: UfragPwd::default(),
|
|
367
|
+
|
|
368
|
+
local_candidates: vec![],
|
|
369
|
+
remote_candidates: vec![],
|
|
370
|
+
|
|
371
|
+
// LRU of outbound Binding request Transaction IDs
|
|
372
|
+
pending_binding_requests: vec![],
|
|
373
|
+
|
|
374
|
+
candidate_types,
|
|
375
|
+
network_types: config.network_types.clone(),
|
|
376
|
+
urls: config.urls.clone(),
|
|
377
|
+
|
|
378
|
+
write_outs: VecDeque::new(),
|
|
379
|
+
event_outs: VecDeque::new(),
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// Restart is also used to initialize the agent for the first time
|
|
383
|
+
if let Err(err) = agent.restart(
|
|
384
|
+
now,
|
|
385
|
+
config.local_ufrag.clone(),
|
|
386
|
+
config.local_pwd.clone(),
|
|
387
|
+
false,
|
|
388
|
+
) {
|
|
389
|
+
let _ = agent.close();
|
|
390
|
+
return Err(err);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
Ok(agent)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/// Adds a new local candidate.
|
|
397
|
+
pub fn add_local_candidate(&mut self, mut c: Candidate) -> Result<bool> {
|
|
398
|
+
// Filter by network type if network_types is configured
|
|
399
|
+
if !self.network_types.is_empty() {
|
|
400
|
+
let candidate_network_type = c.network_type();
|
|
401
|
+
if !self.network_types.contains(&candidate_network_type) {
|
|
402
|
+
debug!(
|
|
403
|
+
"Ignoring local candidate with network type {:?} (not in configured network types: {:?})",
|
|
404
|
+
candidate_network_type, self.network_types
|
|
405
|
+
);
|
|
406
|
+
return Ok(false);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Filter by candidate type if candidate_types is configured.
|
|
411
|
+
let candidate_type = c.candidate_type();
|
|
412
|
+
if !self.candidate_types.is_empty() && !self.candidate_types.contains(&candidate_type) {
|
|
413
|
+
debug!(
|
|
414
|
+
"Ignoring local candidate with type {:?} (not in configured candidate types: {:?})",
|
|
415
|
+
candidate_type, self.candidate_types
|
|
416
|
+
);
|
|
417
|
+
return Ok(false);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if c.candidate_type() == CandidateType::Host
|
|
421
|
+
&& self.mdns_mode == MulticastDnsMode::QueryAndGather
|
|
422
|
+
&& c.network_type == NetworkType::Udp4
|
|
423
|
+
&& self
|
|
424
|
+
.mdns_local_ip
|
|
425
|
+
.is_some_and(|local_ip| local_ip == c.addr().ip())
|
|
426
|
+
{
|
|
427
|
+
// only one .local mDNS host candidate per IPv4 is supported
|
|
428
|
+
// when registered local ip matches, use mdns_local_name to hide local host ip
|
|
429
|
+
trace!(
|
|
430
|
+
"mDNS hides local ip {} with local name {}",
|
|
431
|
+
c.address, self.mdns_local_name
|
|
432
|
+
);
|
|
433
|
+
c.address = self.mdns_local_name.clone();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
for cand in &self.local_candidates {
|
|
437
|
+
if cand.equal(&c) {
|
|
438
|
+
return Ok(false);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
self.local_candidates.push(c);
|
|
443
|
+
let local_index = self.local_candidates.len() - 1;
|
|
444
|
+
|
|
445
|
+
for remote_index in 0..self.remote_candidates.len() {
|
|
446
|
+
if self.local_candidates[local_index]
|
|
447
|
+
.can_pair_with(&self.remote_candidates[remote_index])
|
|
448
|
+
{
|
|
449
|
+
self.add_pair(local_index, remote_index);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
self.request_connectivity_check();
|
|
454
|
+
|
|
455
|
+
Ok(true)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/// Adds a new remote candidate.
|
|
459
|
+
pub fn add_remote_candidate(&mut self, c: Candidate) -> Result<bool> {
|
|
460
|
+
// Filter by network type if network_types is configured
|
|
461
|
+
if !self.network_types.is_empty() {
|
|
462
|
+
let candidate_network_type = c.network_type();
|
|
463
|
+
if !self.network_types.contains(&candidate_network_type) {
|
|
464
|
+
debug!(
|
|
465
|
+
"Ignoring remote candidate with network type {:?} (not in configured network types: {:?})",
|
|
466
|
+
candidate_network_type, self.network_types
|
|
467
|
+
);
|
|
468
|
+
return Ok(false);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// TCP active candidates don't have a listening port - they initiate connections.
|
|
473
|
+
// The remote active side will probe our passive candidates, so we don't need
|
|
474
|
+
// to do anything with remote active candidates.
|
|
475
|
+
if c.tcp_type() == TcpType::Active {
|
|
476
|
+
debug!(
|
|
477
|
+
"Ignoring remote candidate with tcptype active: {}",
|
|
478
|
+
c.address()
|
|
479
|
+
);
|
|
480
|
+
return Ok(false);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// If we have a mDNS Candidate lets fully resolve it before adding it locally
|
|
484
|
+
if c.candidate_type() == CandidateType::Host && c.address().ends_with(".local") {
|
|
485
|
+
if self.mdns_mode == MulticastDnsMode::Disabled {
|
|
486
|
+
warn!(
|
|
487
|
+
"remote mDNS candidate added, but mDNS is disabled: ({})",
|
|
488
|
+
c.address()
|
|
489
|
+
);
|
|
490
|
+
return Ok(false);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if c.candidate_type() != CandidateType::Host {
|
|
494
|
+
return Err(Error::ErrAddressParseFailed);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if let Some(mdns_conn) = &mut self.mdns {
|
|
498
|
+
let query_id = mdns_conn.schedule_query(c.address());
|
|
499
|
+
self.mdns_queries.insert(query_id, c);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return Ok(false);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
self.trigger_request_connectivity_check(vec![c]);
|
|
506
|
+
Ok(true)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
fn trigger_request_connectivity_check(&mut self, remote_candidates: Vec<Candidate>) {
|
|
510
|
+
for c in remote_candidates {
|
|
511
|
+
if !self.remote_candidates.iter().any(|cand| cand.equal(&c)) {
|
|
512
|
+
self.remote_candidates.push(c);
|
|
513
|
+
let remote_index = self.remote_candidates.len() - 1;
|
|
514
|
+
|
|
515
|
+
for local_index in 0..self.local_candidates.len() {
|
|
516
|
+
if self.local_candidates[local_index]
|
|
517
|
+
.can_pair_with(&self.remote_candidates[remote_index])
|
|
518
|
+
{
|
|
519
|
+
self.add_pair(local_index, remote_index);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
self.request_connectivity_check();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/// Sets the credentials of the remote agent.
|
|
529
|
+
pub fn set_remote_credentials(
|
|
530
|
+
&mut self,
|
|
531
|
+
remote_ufrag: String,
|
|
532
|
+
remote_pwd: String,
|
|
533
|
+
) -> Result<()> {
|
|
534
|
+
if remote_ufrag.is_empty() {
|
|
535
|
+
return Err(Error::ErrRemoteUfragEmpty);
|
|
536
|
+
} else if remote_pwd.is_empty() {
|
|
537
|
+
return Err(Error::ErrRemotePwdEmpty);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
self.ufrag_pwd.remote_credentials = Some(Credentials {
|
|
541
|
+
ufrag: remote_ufrag,
|
|
542
|
+
pwd: remote_pwd,
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
Ok(())
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/// Returns the remote credentials.
|
|
549
|
+
pub fn get_remote_credentials(&self) -> Option<&Credentials> {
|
|
550
|
+
self.ufrag_pwd.remote_credentials.as_ref()
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/// Returns the local credentials.
|
|
554
|
+
/// The credentials to advertise, which is what SDP generation wants.
|
|
555
|
+
///
|
|
556
|
+
/// Returns credentials staged by [`Agent::generate_restart_credentials`] when an ICE restart
|
|
557
|
+
/// is pending, so an offer carries the new ufrag/pwd. Inbound STUN validation deliberately
|
|
558
|
+
/// does *not* go through here — it reads `local_credentials` directly, so the live session
|
|
559
|
+
/// keeps working until [`Agent::apply_restart`] installs the new pair.
|
|
560
|
+
pub fn get_local_credentials(&self) -> &Credentials {
|
|
561
|
+
self.ufrag_pwd
|
|
562
|
+
.pending_local_credentials
|
|
563
|
+
.as_ref()
|
|
564
|
+
.unwrap_or(&self.ufrag_pwd.local_credentials)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/// Whether this agent is the controlling one, which decides nomination.
|
|
568
|
+
pub fn role(&self) -> bool {
|
|
569
|
+
self.is_controlling
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/// Sets the controlling role.
|
|
573
|
+
///
|
|
574
|
+
/// Determined by which side offered; the two agents must not agree, or nomination stalls.
|
|
575
|
+
pub fn set_role(&mut self, is_controlling: bool) {
|
|
576
|
+
self.is_controlling = is_controlling;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/// The agent's current connection state.
|
|
580
|
+
pub fn state(&self) -> ConnectionState {
|
|
581
|
+
self.connection_state
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/// Whether a non-STUN datagram on `transport` should be accepted as media.
|
|
585
|
+
///
|
|
586
|
+
/// Guards against accepting media from an address that has not passed a connectivity check.
|
|
587
|
+
pub fn is_valid_non_stun_traffic(&mut self, now: Instant, transport: TransportContext) -> bool {
|
|
588
|
+
self.find_local_candidate(transport.local_addr, transport.transport_protocol)
|
|
589
|
+
.is_some()
|
|
590
|
+
&& self.validate_non_stun_traffic(now, transport.peer_addr)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
fn get_timeout_interval(&self) -> Duration {
|
|
594
|
+
let (check_interval, keepalive_interval, disconnected_timeout, failed_timeout) = (
|
|
595
|
+
self.check_interval,
|
|
596
|
+
self.keepalive_interval,
|
|
597
|
+
self.disconnected_timeout,
|
|
598
|
+
self.failed_timeout,
|
|
599
|
+
);
|
|
600
|
+
let mut interval = DEFAULT_CHECK_INTERVAL;
|
|
601
|
+
|
|
602
|
+
let mut update_interval = |x: Duration| {
|
|
603
|
+
if x != ZERO_DURATION && (interval == ZERO_DURATION || interval > x) {
|
|
604
|
+
interval = x;
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
match self.last_connection_state {
|
|
609
|
+
ConnectionState::New | ConnectionState::Checking => {
|
|
610
|
+
// While connecting, check candidates more frequently
|
|
611
|
+
update_interval(check_interval);
|
|
612
|
+
}
|
|
613
|
+
ConnectionState::Connected | ConnectionState::Disconnected => {
|
|
614
|
+
update_interval(keepalive_interval);
|
|
615
|
+
}
|
|
616
|
+
_ => {}
|
|
617
|
+
};
|
|
618
|
+
// Ensure we run our task loop as quickly as the minimum of our various configured timeouts
|
|
619
|
+
update_interval(disconnected_timeout);
|
|
620
|
+
update_interval(failed_timeout);
|
|
621
|
+
interval
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/// Returns the selected pair (local_candidate, remote_candidate) or none
|
|
625
|
+
pub fn get_selected_candidate_pair(&self) -> Option<(&Candidate, &Candidate)> {
|
|
626
|
+
if let Some(pair_index) = self.get_selected_pair() {
|
|
627
|
+
let candidate_pair = &self.candidate_pairs[pair_index];
|
|
628
|
+
Some((
|
|
629
|
+
&self.local_candidates[candidate_pair.local_index],
|
|
630
|
+
&self.remote_candidates[candidate_pair.remote_index],
|
|
631
|
+
))
|
|
632
|
+
} else if let Some((local, remote)) = &self.restart_media_pair {
|
|
633
|
+
// desklink patch: an ICE restart is in flight and has not nominated
|
|
634
|
+
// its new pair yet, so media keeps flowing on the pair it was on.
|
|
635
|
+
// RFC 8445 keeps the previous selected pair until the new one is
|
|
636
|
+
// validated. The transport layer reads this to stamp outgoing
|
|
637
|
+
// DTLS/SRTP and to pass up inbound packets.
|
|
638
|
+
Some((local, remote))
|
|
639
|
+
} else {
|
|
640
|
+
None
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/// The highest-priority pair that is still usable, whether or not it has been nominated.
|
|
645
|
+
pub fn get_best_available_candidate_pair(&self) -> Option<(&Candidate, &Candidate)> {
|
|
646
|
+
if let Some(pair_index) = self.get_best_available_pair() {
|
|
647
|
+
let candidate_pair = &self.candidate_pairs[pair_index];
|
|
648
|
+
Some((
|
|
649
|
+
&self.local_candidates[candidate_pair.local_index],
|
|
650
|
+
&self.remote_candidates[candidate_pair.remote_index],
|
|
651
|
+
))
|
|
652
|
+
} else {
|
|
653
|
+
None
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/// start connectivity checks
|
|
658
|
+
pub fn start_connectivity_checks(
|
|
659
|
+
&mut self,
|
|
660
|
+
now: Instant,
|
|
661
|
+
is_controlling: bool,
|
|
662
|
+
remote_ufrag: String,
|
|
663
|
+
remote_pwd: String,
|
|
664
|
+
) -> Result<()> {
|
|
665
|
+
debug!(
|
|
666
|
+
"Started agent: isControlling? {}, remoteUfrag: {}, remotePwd: {}",
|
|
667
|
+
is_controlling, remote_ufrag, remote_pwd
|
|
668
|
+
);
|
|
669
|
+
self.set_remote_credentials(remote_ufrag, remote_pwd)?;
|
|
670
|
+
self.is_controlling = is_controlling;
|
|
671
|
+
self.start(now);
|
|
672
|
+
|
|
673
|
+
self.update_connection_state(Some(now), ConnectionState::Checking);
|
|
674
|
+
self.request_connectivity_check();
|
|
675
|
+
|
|
676
|
+
Ok(())
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/// Restarts the ICE Agent with the provided ufrag/pwd
|
|
680
|
+
/// If no ufrag/pwd is provided the Agent will generate one itself.
|
|
681
|
+
/// Stages new local credentials for an ICE restart, without disturbing the live session.
|
|
682
|
+
///
|
|
683
|
+
/// This is the half of an ICE restart that an offerer needs *before* generating SDP: the offer
|
|
684
|
+
/// must carry the new ufrag/pwd. It deliberately does not touch `local_credentials`,
|
|
685
|
+
/// candidate pairs, or connection state, because JSEP requires `createOffer` to be free of
|
|
686
|
+
/// side effects — and because inbound STUN is still being validated against the current
|
|
687
|
+
/// credentials until the local description is applied.
|
|
688
|
+
///
|
|
689
|
+
/// Empty `ufrag` / `pwd` are filled from the crypto provider's RNG. Call
|
|
690
|
+
/// [`Agent::apply_restart`] to install them.
|
|
691
|
+
pub fn generate_restart_credentials(
|
|
692
|
+
&mut self,
|
|
693
|
+
mut ufrag: String,
|
|
694
|
+
mut pwd: String,
|
|
695
|
+
) -> Result<()> {
|
|
696
|
+
if ufrag.is_empty() {
|
|
697
|
+
ufrag = generate_ufrag_with_random(self.crypto_provider.random())?;
|
|
698
|
+
}
|
|
699
|
+
if pwd.is_empty() {
|
|
700
|
+
pwd = generate_pwd_with_random(self.crypto_provider.random())?;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if ufrag.len() * 8 < 24 {
|
|
704
|
+
return Err(Error::ErrLocalUfragInsufficientBits);
|
|
705
|
+
}
|
|
706
|
+
if pwd.len() * 8 < 128 {
|
|
707
|
+
return Err(Error::ErrLocalPwdInsufficientBits);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
self.ufrag_pwd.pending_local_credentials = Some(Credentials { ufrag, pwd });
|
|
711
|
+
|
|
712
|
+
Ok(())
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/// Whether [`Agent::generate_restart_credentials`] has staged a restart that is not yet applied.
|
|
716
|
+
pub fn has_pending_restart(&self) -> bool {
|
|
717
|
+
self.ufrag_pwd.pending_local_credentials.is_some()
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/// Applies a staged ICE restart, tearing down the old session and starting a new one at `now`.
|
|
721
|
+
///
|
|
722
|
+
/// Installs the credentials staged by [`Agent::generate_restart_credentials`], if any, then
|
|
723
|
+
/// discards the remote credentials, pending binding requests, candidate pairs and selected
|
|
724
|
+
/// pair, and restarts the agent's timers. With nothing staged, the current credentials are
|
|
725
|
+
/// kept and only the session is restarted.
|
|
726
|
+
pub fn apply_restart(&mut self, now: Instant, keep_local_candidates: bool) -> Result<()> {
|
|
727
|
+
// desklink patch: remember the pair media is on before the teardown
|
|
728
|
+
// below clears it, so the ICE transport keeps sending and receiving on
|
|
729
|
+
// it until the restarted generation nominates a pair (see
|
|
730
|
+
// `get_selected_candidate_pair`). Cleared when a new pair is selected,
|
|
731
|
+
// or when the connection fails or closes.
|
|
732
|
+
let keep_media_on = self
|
|
733
|
+
.get_selected_candidate_pair()
|
|
734
|
+
.map(|(local, remote)| (local.clone(), remote.clone()));
|
|
735
|
+
self.restart_media_pair = keep_media_on;
|
|
736
|
+
|
|
737
|
+
if let Some(credentials) = self.ufrag_pwd.pending_local_credentials.take() {
|
|
738
|
+
self.ufrag_pwd.local_credentials = credentials;
|
|
739
|
+
}
|
|
740
|
+
self.ufrag_pwd.remote_credentials = None;
|
|
741
|
+
|
|
742
|
+
self.pending_binding_requests = vec![];
|
|
743
|
+
|
|
744
|
+
self.candidate_pairs = vec![];
|
|
745
|
+
|
|
746
|
+
self.set_selected_pair(Some(now), None);
|
|
747
|
+
self.delete_all_candidates(keep_local_candidates);
|
|
748
|
+
self.start(now);
|
|
749
|
+
|
|
750
|
+
// Restart is used by NewAgent. Accept/Connect should be used to move to checking
|
|
751
|
+
// for new Agents
|
|
752
|
+
if self.connection_state != ConnectionState::New {
|
|
753
|
+
self.update_connection_state(Some(now), ConnectionState::Checking);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
Ok(())
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/// Generates and immediately applies an ICE restart.
|
|
760
|
+
///
|
|
761
|
+
/// Equivalent to [`Agent::generate_restart_credentials`] followed by
|
|
762
|
+
/// [`Agent::apply_restart`]. A JSEP-conformant offerer wants the two halves separately —
|
|
763
|
+
/// credentials at `createOffer`, application at `setLocalDescription` — but this remains the
|
|
764
|
+
/// right call for anything that restarts in one step.
|
|
765
|
+
pub fn restart(
|
|
766
|
+
&mut self,
|
|
767
|
+
now: Instant,
|
|
768
|
+
ufrag: String,
|
|
769
|
+
pwd: String,
|
|
770
|
+
keep_local_candidates: bool,
|
|
771
|
+
) -> Result<()> {
|
|
772
|
+
self.generate_restart_credentials(ufrag, pwd)?;
|
|
773
|
+
self.apply_restart(now, keep_local_candidates)
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/// Returns the local candidates.
|
|
777
|
+
pub fn get_local_candidates(&self) -> &[Candidate] {
|
|
778
|
+
&self.local_candidates
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/// The remote candidates this agent has been given, in the order they were added.
|
|
782
|
+
pub fn get_remote_candidates(&self) -> &[Candidate] {
|
|
783
|
+
&self.remote_candidates
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
fn contact(&mut self, now: Instant) {
|
|
787
|
+
// Consume any pending deferred-check request now that we are running one.
|
|
788
|
+
// Reset before the early returns below so a failed/settled agent does not
|
|
789
|
+
// keep asking `poll_timeout` for an immediate wake-up.
|
|
790
|
+
self.force_candidate_contact = false;
|
|
791
|
+
|
|
792
|
+
if self.connection_state == ConnectionState::Failed {
|
|
793
|
+
// The connection is currently failed so don't send any checks
|
|
794
|
+
// In the future it may be restarted though
|
|
795
|
+
self.last_connection_state = self.connection_state;
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
if self.connection_state == ConnectionState::Checking {
|
|
799
|
+
// We have just entered checking for the first time so update our checking timer
|
|
800
|
+
if self.last_connection_state != self.connection_state {
|
|
801
|
+
self.checking_duration = now;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// We have been in checking longer then Disconnect+Failed timeout, set the connection to Failed
|
|
805
|
+
if self.failed_timeout != ZERO_DURATION
|
|
806
|
+
&& now
|
|
807
|
+
.checked_duration_since(self.checking_duration)
|
|
808
|
+
.unwrap_or_else(|| Duration::from_secs(0))
|
|
809
|
+
> self.disconnected_timeout + self.failed_timeout
|
|
810
|
+
{
|
|
811
|
+
self.update_connection_state(Some(now), ConnectionState::Failed);
|
|
812
|
+
self.last_connection_state = self.connection_state;
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
self.contact_candidates(now);
|
|
818
|
+
|
|
819
|
+
self.last_connection_state = self.connection_state;
|
|
820
|
+
self.last_checking_time = now;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
pub(crate) fn update_connection_state(
|
|
824
|
+
&mut self,
|
|
825
|
+
now: Option<Instant>,
|
|
826
|
+
new_state: ConnectionState,
|
|
827
|
+
) {
|
|
828
|
+
if self.connection_state != new_state {
|
|
829
|
+
// Connection has gone to failed, release all gathered candidates
|
|
830
|
+
if new_state == ConnectionState::Failed {
|
|
831
|
+
self.set_selected_pair(now, None);
|
|
832
|
+
self.delete_all_candidates(false);
|
|
833
|
+
}
|
|
834
|
+
// desklink patch: once the connection has failed or closed there is
|
|
835
|
+
// no reachable pair, so media must stop instead of keeping the pair
|
|
836
|
+
// preserved for a restart that will not nominate one.
|
|
837
|
+
if matches!(new_state, ConnectionState::Failed | ConnectionState::Closed) {
|
|
838
|
+
self.restart_media_pair = None;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
info!(
|
|
842
|
+
"[{}]: Setting new connection state: {}",
|
|
843
|
+
self.get_name(),
|
|
844
|
+
new_state
|
|
845
|
+
);
|
|
846
|
+
self.connection_state = new_state;
|
|
847
|
+
if let Some(now) = now {
|
|
848
|
+
self.event_outs.push_back(TaggedEvent {
|
|
849
|
+
now,
|
|
850
|
+
event: Event::ConnectionStateChange(new_state),
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
pub(crate) fn set_selected_pair(&mut self, now: Option<Instant>, selected_pair: Option<usize>) {
|
|
857
|
+
if let Some(pair_index) = selected_pair {
|
|
858
|
+
trace!(
|
|
859
|
+
"[{}]: Set selected candidate pair: {:?}",
|
|
860
|
+
self.get_name(),
|
|
861
|
+
self.candidate_pairs[pair_index]
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
self.candidate_pairs[pair_index].nominated = true;
|
|
865
|
+
self.selected_pair = Some(pair_index);
|
|
866
|
+
// desklink patch: the restarted generation now has a nominated pair,
|
|
867
|
+
// so media moves onto it and the preserved pair is released.
|
|
868
|
+
self.restart_media_pair = None;
|
|
869
|
+
|
|
870
|
+
self.update_connection_state(now, ConnectionState::Connected);
|
|
871
|
+
|
|
872
|
+
// Notify when the selected pair changes
|
|
873
|
+
let candidate_pair = &self.candidate_pairs[pair_index];
|
|
874
|
+
if let Some(now) = now {
|
|
875
|
+
self.event_outs.push_back(TaggedEvent {
|
|
876
|
+
now,
|
|
877
|
+
event: Event::SelectedCandidatePairChange(
|
|
878
|
+
Box::new(self.local_candidates[candidate_pair.local_index].clone()),
|
|
879
|
+
Box::new(self.remote_candidates[candidate_pair.remote_index].clone()),
|
|
880
|
+
),
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
} else {
|
|
884
|
+
self.selected_pair = None;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
pub(crate) fn ping_all_candidates(&mut self, now: Instant) {
|
|
889
|
+
let mut pairs: Vec<(usize, usize)> = vec![];
|
|
890
|
+
|
|
891
|
+
let name = self.get_name().to_string();
|
|
892
|
+
if self.candidate_pairs.is_empty() {
|
|
893
|
+
warn!(
|
|
894
|
+
"[{}]: pingAllCandidates called with no candidate pairs. Connection is not possible yet.",
|
|
895
|
+
name,
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
for p in &mut self.candidate_pairs {
|
|
899
|
+
if p.state == CandidatePairState::Waiting {
|
|
900
|
+
p.state = CandidatePairState::InProgress;
|
|
901
|
+
} else if p.state != CandidatePairState::InProgress {
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
if p.binding_request_count > self.max_binding_requests {
|
|
906
|
+
trace!(
|
|
907
|
+
"[{}]: max requests reached for pair {} (local_addr {} <-> remote_addr {}), marking it as failed",
|
|
908
|
+
name,
|
|
909
|
+
*p,
|
|
910
|
+
self.local_candidates[p.local_index].addr(),
|
|
911
|
+
self.remote_candidates[p.remote_index].addr()
|
|
912
|
+
);
|
|
913
|
+
p.state = CandidatePairState::Failed;
|
|
914
|
+
} else {
|
|
915
|
+
p.binding_request_count += 1;
|
|
916
|
+
let local = p.local_index;
|
|
917
|
+
let remote = p.remote_index;
|
|
918
|
+
pairs.push((local, remote));
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if !pairs.is_empty() {
|
|
923
|
+
trace!(
|
|
924
|
+
"[{}]: pinging all {} candidates",
|
|
925
|
+
self.get_name(),
|
|
926
|
+
pairs.len()
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
for (local, remote) in pairs {
|
|
931
|
+
self.ping_candidate(now, local, remote);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
pub(crate) fn add_pair(&mut self, local_index: usize, remote_index: usize) {
|
|
936
|
+
let p = CandidatePair::new(
|
|
937
|
+
local_index,
|
|
938
|
+
remote_index,
|
|
939
|
+
self.local_candidates[local_index].priority(),
|
|
940
|
+
self.remote_candidates[remote_index].priority(),
|
|
941
|
+
self.is_controlling,
|
|
942
|
+
);
|
|
943
|
+
self.candidate_pairs.push(p);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
pub(crate) fn find_pair(&self, local_index: usize, remote_index: usize) -> Option<usize> {
|
|
947
|
+
for (index, p) in self.candidate_pairs.iter().enumerate() {
|
|
948
|
+
if p.local_index == local_index && p.remote_index == remote_index {
|
|
949
|
+
return Some(index);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
None
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/// Checks if the selected pair is (still) valid.
|
|
956
|
+
/// Note: the caller should hold the agent lock.
|
|
957
|
+
/// Re-evaluates the selected pair's liveness as of `now`.
|
|
958
|
+
///
|
|
959
|
+
/// `now` comes from the caller so that this decision and the keepalive decision below are
|
|
960
|
+
/// made against the same instant as the `contact(now)` that reached them.
|
|
961
|
+
pub(crate) fn validate_selected_pair(&mut self, now: Instant) -> bool {
|
|
962
|
+
let (valid, disconnected_time) = {
|
|
963
|
+
self.selected_pair.as_ref().map_or_else(
|
|
964
|
+
|| (false, Duration::from_secs(0)),
|
|
965
|
+
|&pair_index| {
|
|
966
|
+
let remote_index = self.candidate_pairs[pair_index].remote_index;
|
|
967
|
+
|
|
968
|
+
let last_received = self.remote_candidates[remote_index]
|
|
969
|
+
.last_received()
|
|
970
|
+
.unwrap_or(self.start_time);
|
|
971
|
+
let disconnected_time = now.saturating_duration_since(last_received);
|
|
972
|
+
(true, disconnected_time)
|
|
973
|
+
},
|
|
974
|
+
)
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
if valid {
|
|
978
|
+
// Only allow transitions to fail if a.failedTimeout is non-zero
|
|
979
|
+
let mut total_time_to_failure = self.failed_timeout;
|
|
980
|
+
if total_time_to_failure != Duration::from_secs(0) {
|
|
981
|
+
total_time_to_failure += self.disconnected_timeout;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if total_time_to_failure != Duration::from_secs(0)
|
|
985
|
+
&& disconnected_time > total_time_to_failure
|
|
986
|
+
{
|
|
987
|
+
self.update_connection_state(Some(now), ConnectionState::Failed);
|
|
988
|
+
} else if self.disconnected_timeout != Duration::from_secs(0)
|
|
989
|
+
&& disconnected_time > self.disconnected_timeout
|
|
990
|
+
{
|
|
991
|
+
self.update_connection_state(Some(now), ConnectionState::Disconnected);
|
|
992
|
+
} else {
|
|
993
|
+
self.update_connection_state(Some(now), ConnectionState::Connected);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
valid
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/// Sends STUN Binding Requests to the selected pair at `keepalive_interval` to
|
|
1001
|
+
/// maintain consent freshness (RFC 7675).
|
|
1002
|
+
pub(crate) fn check_keepalive(&mut self, now: Instant) {
|
|
1003
|
+
let (local_index, remote_index, pair_index) = {
|
|
1004
|
+
self.selected_pair
|
|
1005
|
+
.as_ref()
|
|
1006
|
+
.map_or((None, None, None), |&pair_index| {
|
|
1007
|
+
let p = &self.candidate_pairs[pair_index];
|
|
1008
|
+
(Some(p.local_index), Some(p.remote_index), Some(pair_index))
|
|
1009
|
+
})
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
if let (Some(local_index), Some(remote_index), Some(pair_index)) =
|
|
1013
|
+
(local_index, remote_index, pair_index)
|
|
1014
|
+
&& self.keepalive_interval != Duration::from_secs(0)
|
|
1015
|
+
&& now.saturating_duration_since(self.last_consent_sent) >= self.keepalive_interval
|
|
1016
|
+
{
|
|
1017
|
+
self.last_consent_sent = now;
|
|
1018
|
+
self.candidate_pairs[pair_index].on_consent_request_sent();
|
|
1019
|
+
self.ping_candidate(now, local_index, remote_index);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
fn request_connectivity_check(&mut self) {
|
|
1024
|
+
// Defer the connectivity check to `handle_timeout` instead of running it
|
|
1025
|
+
// inline. Running `contact()` here is re-entrant: it can advance the state
|
|
1026
|
+
// to `Failed`, which calls `delete_all_candidates()` and wipes the candidate
|
|
1027
|
+
// and pair vectors while a caller still holds indices into them (for example
|
|
1028
|
+
// `handle_inbound` while adding a peer-reflexive candidate). See issue #88.
|
|
1029
|
+
self.force_candidate_contact = true;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/// Remove all candidates.
|
|
1033
|
+
/// This closes any listening sockets and removes both the local and remote candidate lists.
|
|
1034
|
+
///
|
|
1035
|
+
/// This is used for restarts, failures and on close.
|
|
1036
|
+
pub(crate) fn delete_all_candidates(&mut self, keep_local_candidates: bool) {
|
|
1037
|
+
if !keep_local_candidates {
|
|
1038
|
+
self.local_candidates.clear();
|
|
1039
|
+
}
|
|
1040
|
+
self.remote_candidates.clear();
|
|
1041
|
+
|
|
1042
|
+
// Candidate pairs reference candidates by index, so once the candidates are
|
|
1043
|
+
// removed every pair - and the `selected_pair`/`nominated_pair` indices into
|
|
1044
|
+
// the pair list - is dangling and must be dropped to avoid out-of-bounds
|
|
1045
|
+
// access. `remote_candidates` is always cleared here, so no pair can remain
|
|
1046
|
+
// valid even when the local candidates are kept. See issue #88.
|
|
1047
|
+
self.candidate_pairs.clear();
|
|
1048
|
+
self.selected_pair = None;
|
|
1049
|
+
self.nominated_pair = None;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
pub(crate) fn find_remote_candidate(&self, addr: SocketAddr) -> Option<usize> {
|
|
1053
|
+
for (index, c) in self.remote_candidates.iter().enumerate() {
|
|
1054
|
+
if c.addr() == addr {
|
|
1055
|
+
return Some(index);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
None
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
pub(crate) fn find_local_candidate(
|
|
1062
|
+
&self,
|
|
1063
|
+
addr: SocketAddr,
|
|
1064
|
+
transport_protocol: TransportProtocol,
|
|
1065
|
+
) -> Option<usize> {
|
|
1066
|
+
for (index, c) in self.local_candidates.iter().enumerate() {
|
|
1067
|
+
if c.network_type().to_protocol() != transport_protocol {
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// For TCP active candidates, match by IP only (ignore port).
|
|
1072
|
+
// TCP active candidates use port 9 as placeholder in signaling,
|
|
1073
|
+
// but the actual connection uses an ephemeral port.
|
|
1074
|
+
if c.tcp_type() == TcpType::Active && transport_protocol == TransportProtocol::TCP {
|
|
1075
|
+
if c.addr().ip() == addr.ip() {
|
|
1076
|
+
return Some(index);
|
|
1077
|
+
}
|
|
1078
|
+
} else if c.addr() == addr {
|
|
1079
|
+
return Some(index);
|
|
1080
|
+
} else if let Some(related_address) = c.related_address()
|
|
1081
|
+
&& related_address.address == addr.ip().to_string()
|
|
1082
|
+
&& related_address.port == addr.port()
|
|
1083
|
+
{
|
|
1084
|
+
return Some(index);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
None
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
pub(crate) fn send_binding_request(
|
|
1091
|
+
&mut self,
|
|
1092
|
+
now: Instant,
|
|
1093
|
+
m: &Message,
|
|
1094
|
+
local_index: usize,
|
|
1095
|
+
remote_index: usize,
|
|
1096
|
+
) {
|
|
1097
|
+
trace!(
|
|
1098
|
+
"[{}]: ping STUN from {} to {}",
|
|
1099
|
+
self.get_name(),
|
|
1100
|
+
self.local_candidates[local_index],
|
|
1101
|
+
self.remote_candidates[remote_index],
|
|
1102
|
+
);
|
|
1103
|
+
|
|
1104
|
+
self.invalidate_pending_binding_requests(now);
|
|
1105
|
+
|
|
1106
|
+
self.pending_binding_requests.push(BindingRequest {
|
|
1107
|
+
timestamp: now,
|
|
1108
|
+
transaction_id: m.transaction_id,
|
|
1109
|
+
destination: self.remote_candidates[remote_index].addr(),
|
|
1110
|
+
is_use_candidate: m.contains(ATTR_USE_CANDIDATE),
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
// Track request sent on the candidate pair
|
|
1114
|
+
if let Some(pair_index) = self.find_pair(local_index, remote_index) {
|
|
1115
|
+
self.candidate_pairs[pair_index].on_request_sent();
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
self.send_stun(now, m, local_index, remote_index);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
pub(crate) fn send_binding_success(
|
|
1122
|
+
&mut self,
|
|
1123
|
+
now: Instant,
|
|
1124
|
+
m: &Message,
|
|
1125
|
+
local_index: usize,
|
|
1126
|
+
remote_index: usize,
|
|
1127
|
+
) {
|
|
1128
|
+
let addr = self.remote_candidates[remote_index].addr();
|
|
1129
|
+
let (ip, port) = (addr.ip(), addr.port());
|
|
1130
|
+
let local_pwd = self.ufrag_pwd.local_credentials.pwd.clone();
|
|
1131
|
+
|
|
1132
|
+
let (out, result) = {
|
|
1133
|
+
let mut out = Message::new();
|
|
1134
|
+
let result = out.build(&[
|
|
1135
|
+
Box::new(m.clone()),
|
|
1136
|
+
Box::new(BINDING_SUCCESS),
|
|
1137
|
+
Box::new(XorMappedAddress { ip, port }),
|
|
1138
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(
|
|
1139
|
+
local_pwd,
|
|
1140
|
+
self.crypto_provider.crypto(),
|
|
1141
|
+
)),
|
|
1142
|
+
Box::new(FINGERPRINT),
|
|
1143
|
+
]);
|
|
1144
|
+
(out, result)
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
if let Err(err) = result {
|
|
1148
|
+
warn!(
|
|
1149
|
+
"[{}]: Failed to handle inbound ICE from: {} to: {} error: {}",
|
|
1150
|
+
self.get_name(),
|
|
1151
|
+
self.local_candidates[local_index],
|
|
1152
|
+
self.remote_candidates[remote_index],
|
|
1153
|
+
err
|
|
1154
|
+
);
|
|
1155
|
+
} else {
|
|
1156
|
+
// Track response sent on the candidate pair
|
|
1157
|
+
if let Some(pair_index) = self.find_pair(local_index, remote_index) {
|
|
1158
|
+
self.candidate_pairs[pair_index].on_response_sent();
|
|
1159
|
+
}
|
|
1160
|
+
self.send_stun(now, &out, local_index, remote_index);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/// Sends a 487 (Role Conflict) error response.
|
|
1165
|
+
/// RFC 8445 Section 7.3.1.1
|
|
1166
|
+
pub(crate) fn send_role_conflict_error(
|
|
1167
|
+
&mut self,
|
|
1168
|
+
now: Instant,
|
|
1169
|
+
m: &Message,
|
|
1170
|
+
local_index: usize,
|
|
1171
|
+
remote_index: usize,
|
|
1172
|
+
) {
|
|
1173
|
+
use stun::error_code::*;
|
|
1174
|
+
|
|
1175
|
+
let local_pwd = self.ufrag_pwd.local_credentials.pwd.clone();
|
|
1176
|
+
|
|
1177
|
+
let (out, result) = {
|
|
1178
|
+
let mut out = Message::new();
|
|
1179
|
+
let result = out.build(&[
|
|
1180
|
+
Box::new(m.clone()),
|
|
1181
|
+
Box::new(stun::message::BINDING_ERROR),
|
|
1182
|
+
Box::new(CODE_ROLE_CONFLICT),
|
|
1183
|
+
Box::new(MessageIntegrity::new_short_term_integrity_with_provider(
|
|
1184
|
+
local_pwd,
|
|
1185
|
+
self.crypto_provider.crypto(),
|
|
1186
|
+
)),
|
|
1187
|
+
Box::new(FINGERPRINT),
|
|
1188
|
+
]);
|
|
1189
|
+
(out, result)
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
if let Err(err) = result {
|
|
1193
|
+
warn!(
|
|
1194
|
+
"[{}]: Failed to send role conflict error from: {} to: {} error: {}",
|
|
1195
|
+
self.get_name(),
|
|
1196
|
+
self.local_candidates[local_index],
|
|
1197
|
+
self.remote_candidates[remote_index],
|
|
1198
|
+
err
|
|
1199
|
+
);
|
|
1200
|
+
} else {
|
|
1201
|
+
debug!(
|
|
1202
|
+
"[{}]: Sent 487 Role Conflict error from {} to {}",
|
|
1203
|
+
self.get_name(),
|
|
1204
|
+
self.local_candidates[local_index],
|
|
1205
|
+
self.remote_candidates[remote_index]
|
|
1206
|
+
);
|
|
1207
|
+
self.send_stun(now, &out, local_index, remote_index);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
/// Switches the ICE agent role and recomputes all candidate pair priorities.
|
|
1212
|
+
/// RFC 8445 Section 7.3.1.1
|
|
1213
|
+
pub(crate) fn switch_role(&mut self, now: Instant) {
|
|
1214
|
+
self.is_controlling = !self.is_controlling;
|
|
1215
|
+
|
|
1216
|
+
// Recompute priorities for all candidate pairs
|
|
1217
|
+
// The priority calculation depends on ice_role_controlling
|
|
1218
|
+
for pair in &mut self.candidate_pairs {
|
|
1219
|
+
pair.ice_role_controlling = self.is_controlling;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// Clear nominated pair when switching roles
|
|
1223
|
+
self.nominated_pair = None;
|
|
1224
|
+
|
|
1225
|
+
info!(
|
|
1226
|
+
"[{}]: Role switched, recomputed {} candidate pair priorities",
|
|
1227
|
+
self.get_name(),
|
|
1228
|
+
self.candidate_pairs.len()
|
|
1229
|
+
);
|
|
1230
|
+
|
|
1231
|
+
self.event_outs.push_back(TaggedEvent {
|
|
1232
|
+
now,
|
|
1233
|
+
event: Event::RoleChange(self.is_controlling),
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/// Removes pending binding requests that are over `maxBindingRequestTimeout` old Let HTO be the
|
|
1238
|
+
/// transaction timeout, which SHOULD be 2*RTT if RTT is known or 500 ms otherwise.
|
|
1239
|
+
///
|
|
1240
|
+
/// reference: (IETF ref-8445)[https://tools.ietf.org/html/rfc8445#appendix-B.1].
|
|
1241
|
+
pub(crate) fn invalidate_pending_binding_requests(&mut self, filter_time: Instant) {
|
|
1242
|
+
let pending_binding_requests = &mut self.pending_binding_requests;
|
|
1243
|
+
let initial_size = pending_binding_requests.len();
|
|
1244
|
+
|
|
1245
|
+
let mut temp = vec![];
|
|
1246
|
+
for binding_request in pending_binding_requests.drain(..) {
|
|
1247
|
+
if filter_time
|
|
1248
|
+
.checked_duration_since(binding_request.timestamp)
|
|
1249
|
+
.map(|duration| duration < MAX_BINDING_REQUEST_TIMEOUT)
|
|
1250
|
+
.unwrap_or(true)
|
|
1251
|
+
{
|
|
1252
|
+
temp.push(binding_request);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
*pending_binding_requests = temp;
|
|
1257
|
+
let bind_requests_remaining = pending_binding_requests.len();
|
|
1258
|
+
let bind_requests_removed = initial_size - bind_requests_remaining;
|
|
1259
|
+
if bind_requests_removed > 0 {
|
|
1260
|
+
trace!(
|
|
1261
|
+
"[{}]: Discarded {} binding requests because they expired, still {} remaining",
|
|
1262
|
+
self.get_name(),
|
|
1263
|
+
bind_requests_removed,
|
|
1264
|
+
bind_requests_remaining,
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
/// Assert that the passed `TransactionID` is in our `pendingBindingRequests` and returns the
|
|
1270
|
+
/// destination, If the bindingRequest was valid remove it from our pending cache.
|
|
1271
|
+
pub(crate) fn handle_inbound_binding_success(
|
|
1272
|
+
&mut self,
|
|
1273
|
+
now: Instant,
|
|
1274
|
+
id: TransactionId,
|
|
1275
|
+
) -> Option<BindingRequest> {
|
|
1276
|
+
self.invalidate_pending_binding_requests(now);
|
|
1277
|
+
|
|
1278
|
+
let pending_binding_requests = &mut self.pending_binding_requests;
|
|
1279
|
+
for i in 0..pending_binding_requests.len() {
|
|
1280
|
+
if pending_binding_requests[i].transaction_id == id {
|
|
1281
|
+
let valid_binding_request = pending_binding_requests.remove(i);
|
|
1282
|
+
return Some(valid_binding_request);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
None
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/// Processes STUN traffic from a remote candidate.
|
|
1289
|
+
pub(crate) fn handle_inbound(
|
|
1290
|
+
&mut self,
|
|
1291
|
+
now: Instant,
|
|
1292
|
+
m: &mut Message,
|
|
1293
|
+
local_index: usize,
|
|
1294
|
+
remote_addr: SocketAddr,
|
|
1295
|
+
) -> Result<()> {
|
|
1296
|
+
if m.typ.method != METHOD_BINDING
|
|
1297
|
+
|| !(m.typ.class == CLASS_SUCCESS_RESPONSE
|
|
1298
|
+
|| m.typ.class == CLASS_REQUEST
|
|
1299
|
+
|| m.typ.class == CLASS_INDICATION)
|
|
1300
|
+
{
|
|
1301
|
+
trace!(
|
|
1302
|
+
"[{}]: unhandled STUN from {} to {} class({}) method({})",
|
|
1303
|
+
self.get_name(),
|
|
1304
|
+
remote_addr,
|
|
1305
|
+
self.local_candidates[local_index],
|
|
1306
|
+
m.typ.class,
|
|
1307
|
+
m.typ.method
|
|
1308
|
+
);
|
|
1309
|
+
return Err(Error::ErrUnhandledStunpacket);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// RFC 8445 Section 7.3.1.1 - Detecting and Repairing Role Conflicts
|
|
1313
|
+
if self.is_controlling {
|
|
1314
|
+
if m.contains(ATTR_ICE_CONTROLLING) {
|
|
1315
|
+
// Both agents are controlling - role conflict detected
|
|
1316
|
+
let mut remote_controlling = crate::attributes::control::AttrControlling::default();
|
|
1317
|
+
if let Err(err) = remote_controlling.get_from(m) {
|
|
1318
|
+
warn!(
|
|
1319
|
+
"[{}]: Failed to get remote ICE-CONTROLLING attribute: {}",
|
|
1320
|
+
self.get_name(),
|
|
1321
|
+
err
|
|
1322
|
+
);
|
|
1323
|
+
return Err(err);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
debug!(
|
|
1327
|
+
"[{}]: Role conflict detected (both controlling), local tiebreaker: {}, remote tiebreaker: {}",
|
|
1328
|
+
self.get_name(),
|
|
1329
|
+
self.tie_breaker,
|
|
1330
|
+
remote_controlling.0
|
|
1331
|
+
);
|
|
1332
|
+
|
|
1333
|
+
// Only process if this is a request (not a response)
|
|
1334
|
+
if m.typ.class == CLASS_REQUEST {
|
|
1335
|
+
// Send 487 Role Conflict error
|
|
1336
|
+
if let Some(remote_index) = self.find_remote_candidate(remote_addr) {
|
|
1337
|
+
self.send_role_conflict_error(now, m, local_index, remote_index);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// Compare tiebreakers - if ours is smaller, we switch to controlled
|
|
1341
|
+
if self.tie_breaker < remote_controlling.0 {
|
|
1342
|
+
info!(
|
|
1343
|
+
"[{}]: Switching from controlling to controlled due to role conflict (smaller tiebreaker)",
|
|
1344
|
+
self.get_name()
|
|
1345
|
+
);
|
|
1346
|
+
self.switch_role(now);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
// Continue processing the message after handling role conflict
|
|
1350
|
+
} else if m.contains(ATTR_USE_CANDIDATE) {
|
|
1351
|
+
debug!(
|
|
1352
|
+
"[{}]: useCandidate && a.isControlling == true",
|
|
1353
|
+
self.get_name(),
|
|
1354
|
+
);
|
|
1355
|
+
return Err(Error::ErrUnexpectedStunrequestMessage);
|
|
1356
|
+
}
|
|
1357
|
+
} else if m.contains(ATTR_ICE_CONTROLLED) {
|
|
1358
|
+
// Both agents are controlled - role conflict detected
|
|
1359
|
+
let mut remote_controlled = crate::attributes::control::AttrControlled::default();
|
|
1360
|
+
if let Err(err) = remote_controlled.get_from(m) {
|
|
1361
|
+
warn!(
|
|
1362
|
+
"[{}]: Failed to get remote ICE-CONTROLLED attribute: {}",
|
|
1363
|
+
self.get_name(),
|
|
1364
|
+
err
|
|
1365
|
+
);
|
|
1366
|
+
return Err(err);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
debug!(
|
|
1370
|
+
"[{}]: Role conflict detected (both controlled), local tiebreaker: {}, remote tiebreaker: {}",
|
|
1371
|
+
self.get_name(),
|
|
1372
|
+
self.tie_breaker,
|
|
1373
|
+
remote_controlled.0
|
|
1374
|
+
);
|
|
1375
|
+
|
|
1376
|
+
// Only process if this is a request (not a response)
|
|
1377
|
+
if m.typ.class == CLASS_REQUEST {
|
|
1378
|
+
// Send 487 Role Conflict error
|
|
1379
|
+
if let Some(remote_index) = self.find_remote_candidate(remote_addr) {
|
|
1380
|
+
self.send_role_conflict_error(now, m, local_index, remote_index);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// Compare tiebreakers - if ours is larger, we switch to controlling
|
|
1384
|
+
if self.tie_breaker > remote_controlled.0 {
|
|
1385
|
+
info!(
|
|
1386
|
+
"[{}]: Switching from controlled to controlling due to role conflict (larger tiebreaker)",
|
|
1387
|
+
self.get_name()
|
|
1388
|
+
);
|
|
1389
|
+
self.switch_role(now);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
// Continue processing the message after handling role conflict
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
let Some(remote_credentials) = &self.ufrag_pwd.remote_credentials else {
|
|
1396
|
+
debug!(
|
|
1397
|
+
"[{}]: ufrag_pwd.remote_credentials.is_none",
|
|
1398
|
+
self.get_name(),
|
|
1399
|
+
);
|
|
1400
|
+
return Err(Error::ErrPasswordEmpty);
|
|
1401
|
+
};
|
|
1402
|
+
|
|
1403
|
+
let mut remote_candidate_index = self.find_remote_candidate(remote_addr);
|
|
1404
|
+
if m.typ.class == CLASS_SUCCESS_RESPONSE {
|
|
1405
|
+
if let Err(err) = assert_inbound_message_integrity(
|
|
1406
|
+
m,
|
|
1407
|
+
remote_credentials.pwd.as_bytes(),
|
|
1408
|
+
self.crypto_provider.crypto(),
|
|
1409
|
+
) {
|
|
1410
|
+
warn!(
|
|
1411
|
+
"[{}]: discard message from ({}), {}",
|
|
1412
|
+
self.get_name(),
|
|
1413
|
+
remote_addr,
|
|
1414
|
+
err
|
|
1415
|
+
);
|
|
1416
|
+
return Err(err);
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
if let Some(remote_index) = &remote_candidate_index {
|
|
1420
|
+
self.handle_success_response(now, m, local_index, *remote_index, remote_addr);
|
|
1421
|
+
} else {
|
|
1422
|
+
warn!(
|
|
1423
|
+
"[{}]: discard success message from ({}), no such remote",
|
|
1424
|
+
self.get_name(),
|
|
1425
|
+
remote_addr
|
|
1426
|
+
);
|
|
1427
|
+
return Err(Error::ErrUnhandledStunpacket);
|
|
1428
|
+
}
|
|
1429
|
+
} else if m.typ.class == CLASS_REQUEST {
|
|
1430
|
+
{
|
|
1431
|
+
let username = self.ufrag_pwd.local_credentials.ufrag.clone()
|
|
1432
|
+
+ ":"
|
|
1433
|
+
+ remote_credentials.ufrag.as_str();
|
|
1434
|
+
if let Err(err) = assert_inbound_username(m, &username) {
|
|
1435
|
+
warn!(
|
|
1436
|
+
"[{}]: discard message from ({}), {}",
|
|
1437
|
+
self.get_name(),
|
|
1438
|
+
remote_addr,
|
|
1439
|
+
err
|
|
1440
|
+
);
|
|
1441
|
+
return Err(err);
|
|
1442
|
+
} else if let Err(err) = assert_inbound_message_integrity(
|
|
1443
|
+
m,
|
|
1444
|
+
self.ufrag_pwd.local_credentials.pwd.as_bytes(),
|
|
1445
|
+
self.crypto_provider.crypto(),
|
|
1446
|
+
) {
|
|
1447
|
+
warn!(
|
|
1448
|
+
"[{}]: discard message from ({}), {}",
|
|
1449
|
+
self.get_name(),
|
|
1450
|
+
remote_addr,
|
|
1451
|
+
err
|
|
1452
|
+
);
|
|
1453
|
+
return Err(err);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
if remote_candidate_index.is_none() {
|
|
1458
|
+
// Use the local candidate's network type for the peer-reflexive candidate
|
|
1459
|
+
let network_type = self.local_candidates[local_index].network_type();
|
|
1460
|
+
let (ip, port) = (remote_addr.ip(), remote_addr.port());
|
|
1461
|
+
|
|
1462
|
+
let prflx_candidate_config = CandidatePeerReflexiveConfig {
|
|
1463
|
+
base_config: CandidateConfig {
|
|
1464
|
+
network: network_type.to_string(),
|
|
1465
|
+
address: ip.to_string(),
|
|
1466
|
+
port,
|
|
1467
|
+
component: self.local_candidates[local_index].component(),
|
|
1468
|
+
..CandidateConfig::default()
|
|
1469
|
+
},
|
|
1470
|
+
rel_addr: "".to_owned(),
|
|
1471
|
+
rel_port: 0,
|
|
1472
|
+
};
|
|
1473
|
+
|
|
1474
|
+
match prflx_candidate_config.new_candidate_peer_reflexive() {
|
|
1475
|
+
Ok(prflx_candidate) => {
|
|
1476
|
+
if let Ok(added) = self.add_remote_candidate(prflx_candidate)
|
|
1477
|
+
&& added
|
|
1478
|
+
{
|
|
1479
|
+
// Look the candidate up by address rather than assuming it
|
|
1480
|
+
// is the last element: `add_remote_candidate` may not have
|
|
1481
|
+
// appended it (e.g. a duplicate), and this stays correct if
|
|
1482
|
+
// the vector is ever mutated underneath us. See issue #88.
|
|
1483
|
+
remote_candidate_index = self.find_remote_candidate(remote_addr);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
Err(err) => {
|
|
1487
|
+
error!(
|
|
1488
|
+
"[{}]: Failed to create new remote prflx candidate ({})",
|
|
1489
|
+
self.get_name(),
|
|
1490
|
+
err
|
|
1491
|
+
);
|
|
1492
|
+
return Err(err);
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
|
|
1496
|
+
debug!(
|
|
1497
|
+
"[{}]: adding a new peer-reflexive candidate: {} ",
|
|
1498
|
+
self.get_name(),
|
|
1499
|
+
remote_addr
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
trace!(
|
|
1504
|
+
"[{}]: inbound STUN (Request) from {} to {}",
|
|
1505
|
+
self.get_name(),
|
|
1506
|
+
remote_addr,
|
|
1507
|
+
self.local_candidates[local_index]
|
|
1508
|
+
);
|
|
1509
|
+
|
|
1510
|
+
if let Some(remote_index) = &remote_candidate_index {
|
|
1511
|
+
self.handle_binding_request(now, m, local_index, *remote_index);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
if let Some(remote_index) = remote_candidate_index {
|
|
1516
|
+
self.remote_candidates[remote_index].seen(now, false);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
Ok(())
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// Processes non STUN traffic from a remote candidate, and returns true if it is an actual
|
|
1523
|
+
// remote candidate.
|
|
1524
|
+
pub(crate) fn validate_non_stun_traffic(
|
|
1525
|
+
&mut self,
|
|
1526
|
+
now: Instant,
|
|
1527
|
+
remote_addr: SocketAddr,
|
|
1528
|
+
) -> bool {
|
|
1529
|
+
self.find_remote_candidate(remote_addr)
|
|
1530
|
+
.is_some_and(|remote_index| {
|
|
1531
|
+
self.remote_candidates[remote_index].seen(now, false);
|
|
1532
|
+
true
|
|
1533
|
+
})
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
pub(crate) fn send_stun(
|
|
1537
|
+
&mut self,
|
|
1538
|
+
now: Instant,
|
|
1539
|
+
msg: &Message,
|
|
1540
|
+
local_index: usize,
|
|
1541
|
+
remote_index: usize,
|
|
1542
|
+
) {
|
|
1543
|
+
let peer_addr = self.remote_candidates[remote_index].addr();
|
|
1544
|
+
// RFC 8445 §6.1.2: checks for a (server/peer-)reflexive candidate must
|
|
1545
|
+
// be sent from its base, the bound local socket the candidate was
|
|
1546
|
+
// derived from; the mapped address is not a local socket.
|
|
1547
|
+
let local_addr = self.local_candidates[local_index].base_addr();
|
|
1548
|
+
let transport_protocol = if self.local_candidates[local_index].network_type().is_tcp() {
|
|
1549
|
+
TransportProtocol::TCP
|
|
1550
|
+
} else {
|
|
1551
|
+
TransportProtocol::UDP
|
|
1552
|
+
};
|
|
1553
|
+
|
|
1554
|
+
self.write_outs.push_back(TaggedBytesMut {
|
|
1555
|
+
now,
|
|
1556
|
+
transport: TransportContext {
|
|
1557
|
+
local_addr,
|
|
1558
|
+
peer_addr,
|
|
1559
|
+
ecn: None,
|
|
1560
|
+
transport_protocol,
|
|
1561
|
+
},
|
|
1562
|
+
message: BytesMut::from(&msg.raw[..]),
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1565
|
+
self.local_candidates[local_index].seen(now, true);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
fn handle_inbound_candidate_msg(
|
|
1569
|
+
&mut self,
|
|
1570
|
+
local_index: usize,
|
|
1571
|
+
msg: TaggedBytesMut,
|
|
1572
|
+
) -> Result<()> {
|
|
1573
|
+
if is_stun_message(&msg.message) {
|
|
1574
|
+
let mut m = Message {
|
|
1575
|
+
raw: msg.message.to_vec(),
|
|
1576
|
+
..Message::default()
|
|
1577
|
+
};
|
|
1578
|
+
|
|
1579
|
+
if let Err(err) = m.decode() {
|
|
1580
|
+
warn!(
|
|
1581
|
+
"[{}]: Failed to handle decode ICE from {} to {}: {}",
|
|
1582
|
+
self.get_name(),
|
|
1583
|
+
msg.transport.local_addr,
|
|
1584
|
+
msg.transport.peer_addr,
|
|
1585
|
+
err
|
|
1586
|
+
);
|
|
1587
|
+
Err(err)
|
|
1588
|
+
} else {
|
|
1589
|
+
self.handle_inbound(msg.now, &mut m, local_index, msg.transport.peer_addr)
|
|
1590
|
+
}
|
|
1591
|
+
} else {
|
|
1592
|
+
if !self.validate_non_stun_traffic(msg.now, msg.transport.peer_addr) {
|
|
1593
|
+
warn!(
|
|
1594
|
+
"[{}]: Discarded message, not a valid remote candidate from {}",
|
|
1595
|
+
self.get_name(),
|
|
1596
|
+
msg.transport.peer_addr,
|
|
1597
|
+
);
|
|
1598
|
+
} else {
|
|
1599
|
+
warn!(
|
|
1600
|
+
"[{}]: non-STUN traffic message from a valid remote candidate from {}",
|
|
1601
|
+
self.get_name(),
|
|
1602
|
+
msg.transport.peer_addr
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
Err(Error::ErrNonStunmessage)
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
pub(crate) fn get_name(&self) -> &str {
|
|
1610
|
+
if self.is_controlling {
|
|
1611
|
+
"controlling"
|
|
1612
|
+
} else {
|
|
1613
|
+
"controlled"
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
pub(crate) fn get_selected_pair(&self) -> Option<usize> {
|
|
1618
|
+
self.selected_pair
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
pub(crate) fn get_best_available_pair(&self) -> Option<usize> {
|
|
1622
|
+
let mut best_pair_index: Option<usize> = None;
|
|
1623
|
+
|
|
1624
|
+
for (index, p) in self.candidate_pairs.iter().enumerate() {
|
|
1625
|
+
if p.state == CandidatePairState::Failed {
|
|
1626
|
+
continue;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
if let Some(pair_index) = &mut best_pair_index {
|
|
1630
|
+
let b = &self.candidate_pairs[*pair_index];
|
|
1631
|
+
if b.priority() < p.priority() {
|
|
1632
|
+
*pair_index = index;
|
|
1633
|
+
}
|
|
1634
|
+
} else {
|
|
1635
|
+
best_pair_index = Some(index);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
best_pair_index
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
pub(crate) fn get_best_valid_candidate_pair(&self) -> Option<usize> {
|
|
1643
|
+
let mut best_pair_index: Option<usize> = None;
|
|
1644
|
+
|
|
1645
|
+
for (index, p) in self.candidate_pairs.iter().enumerate() {
|
|
1646
|
+
if p.state != CandidatePairState::Succeeded {
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
if let Some(pair_index) = &mut best_pair_index {
|
|
1651
|
+
let b = &self.candidate_pairs[*pair_index];
|
|
1652
|
+
if b.priority() < p.priority() {
|
|
1653
|
+
*pair_index = index;
|
|
1654
|
+
}
|
|
1655
|
+
} else {
|
|
1656
|
+
best_pair_index = Some(index);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
best_pair_index
|
|
1661
|
+
}
|
|
1662
|
+
}
|