1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
use crate::Candidate;
use crate::CandidateKind;
/// Standard local preference calculation for a candidate.
///
/// This is the default local preference calculation for a candidate.
///
/// It is used to determine the preference of a candidate when there are multiple candidates
/// for a particular component for a particular data stream.
///
/// The preference is calculated based on the candidate type, the IP version of the candidate,
/// and the same_kind counter.
pub fn default_local_preference(c: &Candidate, same_kind: usize) -> u32 {
let ip = c.addr();
// https://datatracker.ietf.org/doc/html/rfc8445#section-5.1.2.1
// The local preference MUST be an integer from 0 (lowest preference) to
// 65535 (highest preference) inclusive. When there is only a single IP
// address, this value SHOULD be set to 65535. If there are multiple
// candidates for a particular component for a particular data stream
// that have the same type, the local preference MUST be unique for each
// one.
// ...
// If an ICE agent is multihomed and has multiple IP addresses, the
// recommendations in [RFC8421] SHOULD be followed. If multiple TURN
// servers are used, local priorities for the candidates obtained from
// the TURN servers are chosen in a similar fashion as for multihomed
// local candidates: the local preference value is used to indicate a
// preference among different servers, but the preference MUST be unique
// for each one.
// ================
//
// The above presupposes that we know all the candidates when we start
// the ice agent. That doesn't work for us, so we deliberately do not
// follow spec. We assign the following intervals for the different
// types of candidates:
//
// 0 - 16384 => relay
// 16384 - 32768 => srflx
// 32768 - 49152 => prflx
// 49152 - 65536 => host
//
// And furthermore we subdivide these to interleave IPv6 with IPv4
// so that odd numbers are ipv6 and even are ipv4.
//
// For host candidates this means:
// 65535 - first ipv6
// 65534 - first ipv4
// 65533 - second ipv6
// 65432 - second ipv4
let counter_start: u32 = {
use CandidateKind::*;
let x = match c.kind() {
Host => 65_535,
PeerReflexive => 49_151,
ServerReflexive => 32_767,
Relayed => 16_383,
};
x - if ip.is_ipv6() { 0 } else { 1 }
};
// For relayed candidates, we add a "punishment" to the local preference
// if the base address differs in the IP version from the allocated address
// of the candidate.
// This punishment ensures that we prefer relayed within the same IP version,
// e.g. IPv4 <> IPv4 over ones that translate between IP version, e.g. IPv4 <> IPv6.
let relay_across_ip_version_punishment = if c.kind() == CandidateKind::Relayed {
if c.local().is_ipv4() != ip.is_ipv4() {
1000
} else {
0
}
} else {
0
};
counter_start - same_kind as u32 * 2 - relay_across_ip_version_punishment
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Candidate;
use std::net::SocketAddr;
fn ipv4_addr(ip: &str) -> SocketAddr {
format!("{}:1234", ip).parse().unwrap()
}
fn ipv6_addr(ip: &str) -> SocketAddr {
format!("[{}]:1234", ip).parse().unwrap()
}
#[test]
fn test_host_candidates_ipv4() {
let addr = ipv4_addr("192.168.1.1");
let candidate = Candidate::host(addr, "udp").unwrap();
// First host candidate (same_kind = 0)
let pref = default_local_preference(&candidate, 0);
assert_eq!(pref, 65534); // 65535 - 1 (IPv4) - 0 * 2
// Second host candidate (same_kind = 1)
let pref = default_local_preference(&candidate, 1);
assert_eq!(pref, 65532); // 65535 - 1 (IPv4) - 1 * 2
// Third host candidate (same_kind = 2)
let pref = default_local_preference(&candidate, 2);
assert_eq!(pref, 65530); // 65535 - 1 (IPv4) - 2 * 2
}
#[test]
fn test_host_candidates_ipv6() {
let addr = ipv6_addr("2001:db8::1");
let candidate = Candidate::host(addr, "udp").unwrap();
// First host candidate (same_kind = 0)
let pref = default_local_preference(&candidate, 0);
assert_eq!(pref, 65535); // 65535 - 0 (IPv6) - 0 * 2
// Second host candidate (same_kind = 1)
let pref = default_local_preference(&candidate, 1);
assert_eq!(pref, 65533); // 65535 - 0 (IPv6) - 1 * 2
// Third host candidate (same_kind = 2)
let pref = default_local_preference(&candidate, 2);
assert_eq!(pref, 65531); // 65535 - 0 (IPv6) - 2 * 2
}
#[test]
fn test_server_reflexive_candidates_ipv4() {
let addr = ipv4_addr("203.0.113.1");
let base = ipv4_addr("192.168.1.1");
let candidate = Candidate::server_reflexive(addr, base, "udp").unwrap();
// First srflx candidate (same_kind = 0)
let pref = default_local_preference(&candidate, 0);
assert_eq!(pref, 32766); // 32767 - 1 (IPv4) - 0 * 2
// Second srflx candidate (same_kind = 1)
let pref = default_local_preference(&candidate, 1);
assert_eq!(pref, 32764); // 32767 - 1 (IPv4) - 1 * 2
}
#[test]
fn test_server_reflexive_candidates_ipv6() {
let addr = ipv6_addr("2001:db8::100");
let base = ipv6_addr("2001:db8::1");
let candidate = Candidate::server_reflexive(addr, base, "udp").unwrap();
// First srflx candidate (same_kind = 0)
let pref = default_local_preference(&candidate, 0);
assert_eq!(pref, 32767); // 32767 - 0 (IPv6) - 0 * 2
// Second srflx candidate (same_kind = 1)
let pref = default_local_preference(&candidate, 1);
assert_eq!(pref, 32765); // 32767 - 0 (IPv6) - 1 * 2
}
#[test]
fn test_peer_reflexive_candidates_ipv4() {
let addr = ipv4_addr("198.51.100.1");
let base = ipv4_addr("192.168.1.1");
let candidate = Candidate::test_peer_rflx(addr, base, "udp");
// First prflx candidate (same_kind = 0)
let pref = default_local_preference(&candidate, 0);
assert_eq!(pref, 49150); // 49151 - 1 (IPv4) - 0 * 2
// Second prflx candidate (same_kind = 1)
let pref = default_local_preference(&candidate, 1);
assert_eq!(pref, 49148); // 49151 - 1 (IPv4) - 1 * 2
}
#[test]
fn test_peer_reflexive_candidates_ipv6() {
let addr = ipv6_addr("2001:db8::200");
let base = ipv6_addr("2001:db8::1");
let candidate = Candidate::test_peer_rflx(addr, base, "udp");
// First prflx candidate (same_kind = 0)
let pref = default_local_preference(&candidate, 0);
assert_eq!(pref, 49151); // 49151 - 0 (IPv6) - 0 * 2
// Second prflx candidate (same_kind = 1)
let pref = default_local_preference(&candidate, 1);
assert_eq!(pref, 49149); // 49151 - 0 (IPv6) - 1 * 2
}
#[test]
fn test_relayed_candidates_ipv4_same_ip_version() {
let addr = ipv4_addr("192.0.2.1");
let local = ipv4_addr("192.168.1.1");
let candidate = Candidate::relayed(addr, local, "udp").unwrap();
// First relay candidate (same_kind = 0, no IP version punishment)
let pref = default_local_preference(&candidate, 0);
assert_eq!(pref, 16382); // 16383 - 1 (IPv4) - 0 * 2 - 0 (no punishment)
// Second relay candidate (same_kind = 1, no IP version punishment)
let pref = default_local_preference(&candidate, 1);
assert_eq!(pref, 16380); // 16383 - 1 (IPv4) - 1 * 2 - 0 (no punishment)
}
#[test]
fn test_relayed_candidates_ipv6_same_ip_version() {
let addr = ipv6_addr("2001:db8::300");
let local = ipv6_addr("2001:db8::1");
let candidate = Candidate::relayed(addr, local, "udp").unwrap();
// First relay candidate (same_kind = 0, no IP version punishment)
let pref = default_local_preference(&candidate, 0);
assert_eq!(pref, 16383); // 16383 - 0 (IPv6) - 0 * 2 - 0 (no punishment)
// Second relay candidate (same_kind = 1, no IP version punishment)
let pref = default_local_preference(&candidate, 1);
assert_eq!(pref, 16381); // 16383 - 0 (IPv6) - 1 * 2 - 0 (no punishment)
}
#[test]
fn test_relayed_candidates_cross_ip_version_punishment() {
// IPv4 allocated address with IPv6 local address
let addr = ipv4_addr("192.0.2.1");
let local = ipv6_addr("2001:db8::1");
let candidate = Candidate::relayed(addr, local, "udp").unwrap();
// First relay candidate with IP version punishment
let pref = default_local_preference(&candidate, 0);
assert_eq!(pref, 15382); // 16383 - 1 (IPv4) - 0 * 2 - 1000 (punishment)
// Second relay candidate with IP version punishment
let pref = default_local_preference(&candidate, 1);
assert_eq!(pref, 15380); // 16383 - 1 (IPv4) - 1 * 2 - 1000 (punishment)
}
#[test]
fn test_relayed_candidates_cross_ip_version_punishment_reverse() {
// IPv6 allocated address with IPv4 local address
let addr = ipv6_addr("2001:db8::300");
let local = ipv4_addr("192.168.1.1");
let candidate = Candidate::relayed(addr, local, "udp").unwrap();
// First relay candidate with IP version punishment
let pref = default_local_preference(&candidate, 0);
assert_eq!(pref, 15383); // 16383 - 0 (IPv6) - 0 * 2 - 1000 (punishment)
// Second relay candidate with IP version punishment
let pref = default_local_preference(&candidate, 1);
assert_eq!(pref, 15381); // 16383 - 0 (IPv6) - 1 * 2 - 1000 (punishment)
}
#[test]
fn test_candidate_type_ordering() {
// Test that different candidate types maintain proper ordering
let ipv4_addr = ipv4_addr("192.168.1.1");
let ipv6_addr = ipv6_addr("2001:db8::1");
let local = ipv4_addr;
let host_ipv4 = Candidate::host(ipv4_addr, "udp").unwrap();
let host_ipv6 = Candidate::host(ipv6_addr, "udp").unwrap();
let srflx_ipv4 = Candidate::server_reflexive(ipv4_addr, local, "udp").unwrap();
let srflx_ipv6 = Candidate::server_reflexive(ipv6_addr, ipv6_addr, "udp").unwrap();
let prflx_ipv4 = Candidate::test_peer_rflx(ipv4_addr, local, "udp");
let prflx_ipv6 = Candidate::test_peer_rflx(ipv6_addr, ipv6_addr, "udp");
let relay_ipv4 = Candidate::relayed(ipv4_addr, local, "udp").unwrap();
let relay_ipv6 = Candidate::relayed(ipv6_addr, ipv6_addr, "udp").unwrap();
let same_kind = 0;
let host_ipv4_pref = default_local_preference(&host_ipv4, same_kind);
let host_ipv6_pref = default_local_preference(&host_ipv6, same_kind);
let srflx_ipv4_pref = default_local_preference(&srflx_ipv4, same_kind);
let srflx_ipv6_pref = default_local_preference(&srflx_ipv6, same_kind);
let prflx_ipv4_pref = default_local_preference(&prflx_ipv4, same_kind);
let prflx_ipv6_pref = default_local_preference(&prflx_ipv6, same_kind);
let relay_ipv4_pref = default_local_preference(&relay_ipv4, same_kind);
let relay_ipv6_pref = default_local_preference(&relay_ipv6, same_kind);
// Host candidates should have highest preference
assert!(host_ipv4_pref > srflx_ipv4_pref);
assert!(host_ipv6_pref > srflx_ipv6_pref);
assert!(host_ipv4_pref > prflx_ipv4_pref);
assert!(host_ipv6_pref > prflx_ipv6_pref);
assert!(host_ipv4_pref > relay_ipv4_pref);
assert!(host_ipv6_pref > relay_ipv6_pref);
// Peer reflexive candidates should have higher preference than server reflexive
assert!(prflx_ipv4_pref > srflx_ipv4_pref);
assert!(prflx_ipv6_pref > srflx_ipv6_pref);
// Server reflexive candidates should have higher preference than relayed
assert!(srflx_ipv4_pref > relay_ipv4_pref);
assert!(srflx_ipv6_pref > relay_ipv6_pref);
// Peer reflexive candidates should have higher preference than relayed
assert!(prflx_ipv4_pref > relay_ipv4_pref);
assert!(prflx_ipv6_pref > relay_ipv6_pref);
// IPv6 should have higher preference than IPv4 within the same type
assert!(host_ipv6_pref > host_ipv4_pref);
assert!(srflx_ipv6_pref > srflx_ipv4_pref);
assert!(prflx_ipv6_pref > prflx_ipv4_pref);
assert!(relay_ipv6_pref > relay_ipv4_pref);
}
#[test]
fn test_ipv4_ipv6_interleaving() {
// Test that IPv6 gets odd numbers and IPv4 gets even numbers
let ipv4_addr = ipv4_addr("192.168.1.1");
let ipv6_addr = ipv6_addr("2001:db8::1");
let host_ipv4 = Candidate::host(ipv4_addr, "udp").unwrap();
let host_ipv6 = Candidate::host(ipv6_addr, "udp").unwrap();
let ipv4_pref = default_local_preference(&host_ipv4, 0);
let ipv6_pref = default_local_preference(&host_ipv6, 0);
// IPv4 should get even number (65534), IPv6 should get odd number (65535)
assert_eq!(ipv4_pref % 2, 0, "IPv4 preference should be even");
assert_eq!(ipv6_pref % 2, 1, "IPv6 preference should be odd");
assert_eq!(
ipv6_pref - ipv4_pref,
1,
"IPv6 should be exactly 1 higher than IPv4"
);
}
#[test]
fn test_same_kind_counter_effect() {
let addr = ipv4_addr("192.168.1.1");
let candidate = Candidate::host(addr, "udp").unwrap();
// Test that same_kind counter decreases preference by 2 each time
let pref0 = default_local_preference(&candidate, 0);
let pref1 = default_local_preference(&candidate, 1);
let pref2 = default_local_preference(&candidate, 2);
let pref3 = default_local_preference(&candidate, 3);
assert_eq!(
pref0 - pref1,
2,
"Preference should decrease by 2 for each same_kind increment"
);
assert_eq!(
pref1 - pref2,
2,
"Preference should decrease by 2 for each same_kind increment"
);
assert_eq!(
pref2 - pref3,
2,
"Preference should decrease by 2 for each same_kind increment"
);
}
}