/src/neqo/neqo-transport/src/ecn.rs
Line | Count | Source |
1 | | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
2 | | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
3 | | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
4 | | // option. This file may not be copied, modified, or distributed |
5 | | // except according to those terms. |
6 | | |
7 | | use std::ops::{AddAssign, Deref, DerefMut, Sub}; |
8 | | |
9 | | use enum_map::{Enum, EnumMap}; |
10 | | use neqo_common::{qdebug, qinfo, Ecn}; |
11 | | |
12 | | use crate::{packet, recovery::sent, Stats}; |
13 | | |
14 | | /// The number of packets to use for testing a path for ECN capability. |
15 | | pub(crate) const TEST_COUNT: usize = 10; |
16 | | |
17 | | /// The number of packets to use for testing a path for ECN capability when exchanging |
18 | | /// Initials during the handshake. This is a lower number than [`TEST_COUNT`] to avoid |
19 | | /// unnecessarily delaying the handshake; we would otherwise double the PTO [`TEST_COUNT`] |
20 | | /// times. |
21 | | const TEST_COUNT_INITIAL_PHASE: usize = 3; |
22 | | |
23 | | /// The state information related to testing a path for ECN capability. |
24 | | /// See RFC9000, Appendix A.4. |
25 | | #[derive(Debug, PartialEq, Clone, Copy, Default)] |
26 | | enum ValidationState { |
27 | | /// ECN validation not started yet. Reason might e.g. be still handshaking |
28 | | /// or not being the primary path. |
29 | | #[default] |
30 | | NotStarted, |
31 | | /// The path is currently being tested for ECN capability, with the number of probes sent so |
32 | | /// far on the path during the ECN validation. |
33 | | Testing { |
34 | | probes_sent: usize, |
35 | | initial_probes_acked: usize, |
36 | | initial_probes_lost: usize, |
37 | | }, |
38 | | /// The validation test has concluded but the path's ECN capability is not yet known. |
39 | | Unknown, |
40 | | /// The path is known to **not** be ECN capable. |
41 | | Failed(ValidationError), |
42 | | /// The path is known to be ECN capable. |
43 | | Capable, |
44 | | } |
45 | | |
46 | | impl ValidationState { |
47 | 0 | fn set(&mut self, new: Self, stats: &mut Stats) { |
48 | 0 | let old = std::mem::replace(self, new); |
49 | | |
50 | 0 | match old { |
51 | 0 | Self::NotStarted | Self::Testing { .. } | Self::Unknown => {} |
52 | 0 | Self::Failed(_) => debug_assert!(false, "Failed is a terminal state"), |
53 | 0 | Self::Capable => stats.ecn_path_validation[ValidationOutcome::Capable] -= 1, |
54 | | } |
55 | 0 | match new { |
56 | 0 | Self::NotStarted | Self::Testing { .. } | Self::Unknown => {} |
57 | 0 | Self::Failed(error) => { |
58 | 0 | stats.ecn_path_validation[ValidationOutcome::NotCapable(error)] += 1; |
59 | 0 | } |
60 | 0 | Self::Capable => stats.ecn_path_validation[ValidationOutcome::Capable] += 1, |
61 | | } |
62 | 0 | } |
63 | | } |
64 | | |
65 | | /// The counts for different ECN marks. |
66 | | /// |
67 | | /// Note: [`Count`] is used both for outgoing UDP datagrams, returned by |
68 | | /// remote through QUIC ACKs and for incoming UDP datagrams, read from IP TOS |
69 | | /// header. In the former case, given that QUIC ACKs only carry |
70 | | /// [`Ecn::Ect0`], [`Ecn::Ect1`] and [`Ecn::Ce`], but never |
71 | | /// [`Ecn::NotEct`], the [`Ecn::NotEct`] value will always be 0. |
72 | | /// |
73 | | /// See also <https://www.rfc-editor.org/rfc/rfc9000.html#section-19.3.2>. |
74 | | #[derive(PartialEq, Eq, Debug, Clone, Copy, Default)] |
75 | | pub struct Count(EnumMap<Ecn, u64>); |
76 | | |
77 | | impl Deref for Count { |
78 | | type Target = EnumMap<Ecn, u64>; |
79 | | |
80 | 2.75k | fn deref(&self) -> &Self::Target { |
81 | 2.75k | &self.0 |
82 | 2.75k | } |
83 | | } |
84 | | |
85 | | impl DerefMut for Count { |
86 | 3.14k | fn deref_mut(&mut self) -> &mut Self::Target { |
87 | 3.14k | &mut self.0 |
88 | 3.14k | } |
89 | | } |
90 | | |
91 | | impl Count { |
92 | | #[must_use] |
93 | 8 | pub const fn new(not_ect: u64, ect0: u64, ect1: u64, ce: u64) -> Self { |
94 | | // Yes, the enum array order is different from the argument order. |
95 | 8 | Self(EnumMap::from_array([not_ect, ect1, ect0, ce])) |
96 | 8 | } |
97 | | |
98 | | /// Whether any of the ECT(0), ECT(1) or CE counts are non-zero. |
99 | | #[must_use] |
100 | 918 | pub fn is_some(&self) -> bool { |
101 | 918 | self[Ecn::Ect0] > 0 || self[Ecn::Ect1] > 0 || self[Ecn::Ce] > 0 |
102 | 918 | } |
103 | | |
104 | | /// Whether all of the ECN counts are zero (including Not-ECT.) |
105 | | #[must_use] |
106 | 0 | pub fn is_empty(&self) -> bool { |
107 | 0 | self.iter().all(|(_, count)| *count == 0) |
108 | 0 | } |
109 | | } |
110 | | |
111 | | impl Sub<Self> for Count { |
112 | | type Output = Self; |
113 | | |
114 | | /// Subtract the ECN counts in `other` from `self`. |
115 | 0 | fn sub(self, rhs: Self) -> Self { |
116 | 0 | let mut diff = Self::default(); |
117 | 0 | for (ecn, count) in &mut *diff { |
118 | 0 | *count = self[ecn].saturating_sub(rhs[ecn]); |
119 | 0 | } |
120 | 0 | diff |
121 | 0 | } |
122 | | } |
123 | | |
124 | | impl AddAssign<Ecn> for Count { |
125 | 3.14k | fn add_assign(&mut self, rhs: Ecn) { |
126 | 3.14k | self[rhs] += 1; |
127 | 3.14k | } |
128 | | } |
129 | | |
130 | | #[derive(PartialEq, Eq, Debug, Clone, Copy, Default)] |
131 | | pub struct ValidationCount(EnumMap<ValidationOutcome, u64>); |
132 | | |
133 | | impl Deref for ValidationCount { |
134 | | type Target = EnumMap<ValidationOutcome, u64>; |
135 | | |
136 | 0 | fn deref(&self) -> &Self::Target { |
137 | 0 | &self.0 |
138 | 0 | } |
139 | | } |
140 | | |
141 | | impl DerefMut for ValidationCount { |
142 | 0 | fn deref_mut(&mut self) -> &mut Self::Target { |
143 | 0 | &mut self.0 |
144 | 0 | } |
145 | | } |
146 | | |
147 | | #[derive(Debug, Clone, Copy, Enum, PartialEq, Eq)] |
148 | | pub enum ValidationError { |
149 | | BlackHole, |
150 | | Bleaching, |
151 | | ReceivedUnsentECT1, |
152 | | } |
153 | | |
154 | | #[derive(Debug, Clone, Copy, Enum, PartialEq, Eq)] |
155 | | pub enum ValidationOutcome { |
156 | | Capable, |
157 | | NotCapable(ValidationError), |
158 | | } |
159 | | |
160 | | #[derive(Debug, Default)] |
161 | | pub(crate) struct Info { |
162 | | /// The current state of ECN validation on this path. |
163 | | state: ValidationState, |
164 | | |
165 | | /// The largest ACK seen so far. |
166 | | largest_acked: packet::Number, |
167 | | |
168 | | /// The ECN counts from the last ACK frame that increased `largest_acked`. |
169 | | baseline: Count, |
170 | | } |
171 | | |
172 | | impl Info { |
173 | 0 | pub(crate) fn start(&mut self, stats: &mut Stats) { |
174 | 0 | if !matches!(self.state, ValidationState::NotStarted) { |
175 | 0 | return; |
176 | 0 | } |
177 | | |
178 | 0 | self.state.set( |
179 | 0 | ValidationState::Testing { |
180 | 0 | probes_sent: 0, |
181 | 0 | initial_probes_acked: 0, |
182 | 0 | initial_probes_lost: 0, |
183 | 0 | }, |
184 | 0 | stats, |
185 | | ); |
186 | 0 | } |
187 | | |
188 | | /// Set the baseline (= the ECN counts from the last ACK Frame). |
189 | 0 | pub(crate) fn set_baseline(&mut self, baseline: Count) { |
190 | 0 | self.baseline = baseline; |
191 | 0 | } |
192 | | |
193 | | /// Expose the current baseline. |
194 | 0 | pub(crate) const fn baseline(&self) -> Count { |
195 | 0 | self.baseline |
196 | 0 | } |
197 | | |
198 | | /// Count the number of packets sent out on this path during ECN validation. |
199 | | /// Exit ECN validation if the number of packets sent exceeds `TEST_COUNT`. |
200 | | /// We do not implement the part of the RFC that says to exit ECN validation if the time since |
201 | | /// the start of ECN validation exceeds 3 * PTO, since this seems to happen much too quickly. |
202 | 1.76k | pub(crate) fn on_packet_sent(&mut self, num_datagrams: usize, stats: &mut Stats) { |
203 | 1.76k | if let ValidationState::Testing { probes_sent, .. } = &mut self.state { |
204 | 0 | *probes_sent += num_datagrams; |
205 | 0 | qdebug!("ECN probing: sent {probes_sent} probes"); |
206 | 0 | if *probes_sent >= TEST_COUNT { |
207 | 0 | qdebug!("ECN probing concluded with {probes_sent} probes sent"); |
208 | 0 | self.state.set(ValidationState::Unknown, stats); |
209 | 0 | } |
210 | 1.76k | } |
211 | 1.76k | } |
212 | | |
213 | | /// Disable ECN. |
214 | 0 | pub(crate) fn disable_ecn(&mut self, stats: &mut Stats, reason: ValidationError) { |
215 | 0 | self.state.set(ValidationState::Failed(reason), stats); |
216 | 0 | } |
217 | | |
218 | | /// Process ECN counts from an ACK frame. |
219 | | /// |
220 | | /// Returns whether ECN counts contain new valid ECN CE marks. |
221 | 0 | pub(crate) fn on_packets_acked( |
222 | 0 | &mut self, |
223 | 0 | acked_packets: &[sent::Packet], |
224 | 0 | ack_ecn: Option<&Count>, |
225 | 0 | stats: &mut Stats, |
226 | 0 | ) -> bool { |
227 | 0 | let prev_baseline = self.baseline; |
228 | | |
229 | 0 | self.validate_ack_ecn_and_update(acked_packets, ack_ecn, stats); |
230 | | |
231 | 0 | matches!(self.state, ValidationState::Capable) |
232 | 0 | && (self.baseline - prev_baseline)[Ecn::Ce] > 0 |
233 | 0 | } |
234 | | |
235 | | /// An [`Ecn::Ect0`] marked packet has been acked. |
236 | 0 | pub(crate) fn acked_ecn(&mut self) { |
237 | | if let ValidationState::Testing { |
238 | 0 | initial_probes_acked: probes_acked, |
239 | | .. |
240 | 0 | } = &mut self.state |
241 | 0 | { |
242 | 0 | *probes_acked += 1; |
243 | 0 | } |
244 | 0 | } |
245 | | |
246 | | /// An [`Ecn::Ect0`] marked packet has been declared lost. |
247 | 0 | pub(crate) fn lost_ecn(&mut self, stats: &mut Stats) { |
248 | | if let ValidationState::Testing { |
249 | 0 | initial_probes_acked: probes_acked, |
250 | 0 | initial_probes_lost: probes_lost, |
251 | | .. |
252 | 0 | } = &mut self.state |
253 | | { |
254 | 0 | *probes_lost += 1; |
255 | | // If we have lost all initial probes a bunch of times, we can conclude that the path |
256 | | // is not ECN capable and likely drops all ECN marked packets. |
257 | 0 | if *probes_acked == 0 && *probes_lost == TEST_COUNT_INITIAL_PHASE { |
258 | 0 | qdebug!( |
259 | | "ECN validation failed, all {probes_lost} initial marked packets were lost" |
260 | | ); |
261 | 0 | self.disable_ecn(stats, ValidationError::BlackHole); |
262 | 0 | } |
263 | 0 | } |
264 | 0 | } |
265 | | |
266 | | /// After the ECN validation test has ended, check if the path is ECN capable. |
267 | 0 | fn validate_ack_ecn_and_update( |
268 | 0 | &mut self, |
269 | 0 | acked_packets: &[sent::Packet], |
270 | 0 | ack_ecn: Option<&Count>, |
271 | 0 | stats: &mut Stats, |
272 | 0 | ) { |
273 | | // RFC 9000, Section 13.4.2.1: |
274 | | // |
275 | | // > Validating ECN counts from reordered ACK frames can result in failure. An endpoint MUST |
276 | | // > NOT fail ECN validation as a result of processing an ACK frame that does not increase |
277 | | // > the largest acknowledged packet number. |
278 | 0 | let largest_acked = acked_packets.first().expect("must be there"); |
279 | 0 | if largest_acked.pn() <= self.largest_acked { |
280 | 0 | return; |
281 | 0 | } |
282 | | |
283 | | // RFC 9000, Appendix A.4: |
284 | | // |
285 | | // > From the "unknown" state, successful validation of the ECN counts in an ACK frame |
286 | | // > (see Section 13.4.2.1) causes the ECN state for the path to become "capable", unless |
287 | | // > no marked packet has been acknowledged. |
288 | 0 | match self.state { |
289 | | ValidationState::NotStarted |
290 | | | ValidationState::Testing { .. } |
291 | 0 | | ValidationState::Failed(_) => return, |
292 | 0 | ValidationState::Unknown | ValidationState::Capable => {} |
293 | | } |
294 | | |
295 | | // RFC 9000, Section 13.4.2.1: |
296 | | // |
297 | | // > An endpoint that receives an ACK frame with ECN counts therefore validates |
298 | | // > the counts before using them. It performs this validation by comparing newly |
299 | | // > received counts against those from the last successfully processed ACK frame. |
300 | | // |
301 | | // > If an ACK frame newly acknowledges a packet that the endpoint sent with |
302 | | // > either the ECT(0) or ECT(1) codepoint set, ECN validation fails if the |
303 | | // > corresponding ECN counts are not present in the ACK frame. |
304 | 0 | let Some(ack_ecn) = ack_ecn else { |
305 | 0 | qinfo!("ECN validation failed, no ECN counts in ACK frame"); |
306 | 0 | self.disable_ecn(stats, ValidationError::Bleaching); |
307 | 0 | return; |
308 | | }; |
309 | 0 | let ack_ecn = *ack_ecn; |
310 | 0 | stats.ecn_tx_acked[largest_acked.packet_type()] = ack_ecn; |
311 | | |
312 | | // > ECN validation also fails if the sum of the increase in ECT(0) and ECN-CE counts is |
313 | | // > less than the number of newly acknowledged packets that were originally sent with an |
314 | | // > ECT(0) marking. |
315 | 0 | let newly_acked_sent_with_ect0: u64 = acked_packets |
316 | 0 | .iter() |
317 | 0 | .filter(|p| p.ecn_marked_ect0()) |
318 | 0 | .count() |
319 | 0 | .try_into() |
320 | 0 | .expect("usize fits into u64"); |
321 | 0 | let ecn_diff = ack_ecn - self.baseline; |
322 | 0 | let sum_inc = ecn_diff[Ecn::Ect0] + ecn_diff[Ecn::Ce]; |
323 | 0 | if sum_inc < newly_acked_sent_with_ect0 { |
324 | 0 | qinfo!( |
325 | | "ECN validation failed, ACK counted {sum_inc} new marks, but {newly_acked_sent_with_ect0} of newly acked packets were sent with ECT(0)" |
326 | | ); |
327 | 0 | self.disable_ecn(stats, ValidationError::Bleaching); |
328 | 0 | } else if ecn_diff[Ecn::Ect1] > 0 { |
329 | 0 | qinfo!("ECN validation failed, ACK counted ECT(1) marks that were never sent"); |
330 | 0 | self.disable_ecn(stats, ValidationError::ReceivedUnsentECT1); |
331 | 0 | } else if self.state != ValidationState::Capable { |
332 | 0 | qinfo!("ECN validation succeeded, path is capable"); |
333 | 0 | self.state.set(ValidationState::Capable, stats); |
334 | 0 | } |
335 | 0 | self.baseline = ack_ecn; |
336 | 0 | self.largest_acked = largest_acked.pn(); |
337 | 0 | } |
338 | | |
339 | 1.76k | pub(crate) const fn is_marking(&self) -> bool { |
340 | 1.76k | match self.state { |
341 | 0 | ValidationState::Testing { .. } | ValidationState::Capable => true, |
342 | | ValidationState::NotStarted | ValidationState::Failed(_) | ValidationState::Unknown => { |
343 | 1.76k | false |
344 | | } |
345 | | } |
346 | 1.76k | } |
347 | | |
348 | | /// The ECN mark to use for an outgoing UDP datagram. |
349 | 1.76k | pub(crate) const fn ecn_mark(&self) -> Ecn { |
350 | 1.76k | if self.is_marking() { |
351 | 0 | Ecn::Ect0 |
352 | | } else { |
353 | 1.76k | Ecn::NotEct |
354 | | } |
355 | 1.76k | } |
356 | | } |
357 | | |
358 | | #[cfg(test)] |
359 | | #[cfg_attr(coverage_nightly, coverage(off))] |
360 | | mod tests { |
361 | | use super::Count; |
362 | | |
363 | | #[test] |
364 | | fn count_predicates() { |
365 | | assert!(Count::default().is_empty()); |
366 | | assert!(!Count::default().is_some()); |
367 | | assert!(!Count::new(0, 1, 0, 0).is_empty()); |
368 | | assert!(Count::new(0, 1, 0, 0).is_some()); // ect1 > 0 |
369 | | assert!(Count::new(0, 0, 1, 0).is_some()); // ect0 > 0 |
370 | | assert!(Count::new(0, 0, 0, 1).is_some()); // ce > 0 |
371 | | assert!(!Count::new(1, 0, 0, 0).is_empty()); |
372 | | assert!(!Count::new(1, 0, 0, 0).is_some()); // not_ect alone |
373 | | } |
374 | | } |