|
/// After the ECN validation test has ended, check if the path is ECN capable. |
|
fn validate_ack_ecn_and_update( |
|
&mut self, |
|
acked_packets: &[sent::Packet], |
|
ack_ecn: Option<&Count>, |
|
stats: &mut Stats, |
|
) { |
|
// RFC 9000, Section 13.4.2.1: |
|
// |
|
// > Validating ECN counts from reordered ACK frames can result in failure. An endpoint MUST |
|
// > NOT fail ECN validation as a result of processing an ACK frame that does not increase |
|
// > the largest acknowledged packet number. |
|
let largest_acked = acked_packets.first().expect("must be there"); |
|
if largest_acked.pn() <= self.largest_acked { |
|
return; |
|
} |
|
|
|
// RFC 9000, Appendix A.4: |
|
// |
|
// > From the "unknown" state, successful validation of the ECN counts in an ACK frame |
|
// > (see Section 13.4.2.1) causes the ECN state for the path to become "capable", unless |
|
// > no marked packet has been acknowledged. |
|
match self.state { |
|
ValidationState::NotStarted |
|
| ValidationState::Testing { .. } |
|
| ValidationState::Failed(_) => return, |
|
ValidationState::Unknown | ValidationState::Capable => {} |
|
} |
|
|
|
// RFC 9000, Section 13.4.2.1: |
|
// |
|
// > An endpoint that receives an ACK frame with ECN counts therefore validates |
|
// > the counts before using them. It performs this validation by comparing newly |
|
// > received counts against those from the last successfully processed ACK frame. |
|
// |
|
// > If an ACK frame newly acknowledges a packet that the endpoint sent with |
|
// > either the ECT(0) or ECT(1) codepoint set, ECN validation fails if the |
|
// > corresponding ECN counts are not present in the ACK frame. |
|
let Some(ack_ecn) = ack_ecn else { |
|
qinfo!("ECN validation failed, no ECN counts in ACK frame"); |
|
self.disable_ecn(stats, ValidationError::Bleaching); |
|
return; |
|
}; |
|
let ack_ecn = *ack_ecn; |
|
stats.ecn_tx_acked[largest_acked.packet_type()] = ack_ecn; |
|
|
|
// > ECN validation also fails if the sum of the increase in ECT(0) and ECN-CE counts is |
|
// > less than the number of newly acknowledged packets that were originally sent with an |
|
// > ECT(0) marking. |
|
let newly_acked_sent_with_ect0: u64 = acked_packets |
|
.iter() |
|
.filter(|p| p.ecn_marked_ect0()) |
|
.count() |
|
.try_into() |
|
.expect("usize fits into u64"); |
|
let ecn_diff = ack_ecn - self.baseline; |
|
let sum_inc = ecn_diff[Ecn::Ect0] + ecn_diff[Ecn::Ce]; |
|
if sum_inc < newly_acked_sent_with_ect0 { |
|
qinfo!( |
|
"ECN validation failed, ACK counted {sum_inc} new marks, but {newly_acked_sent_with_ect0} of newly acked packets were sent with ECT(0)" |
|
); |
|
self.disable_ecn(stats, ValidationError::Bleaching); |
|
} else if ecn_diff[Ecn::Ect1] > 0 { |
|
qinfo!("ECN validation failed, ACK counted ECT(1) marks that were never sent"); |
|
self.disable_ecn(stats, ValidationError::ReceivedUnsentECT1); |
|
} else if self.state != ValidationState::Capable { |
|
qinfo!("ECN validation succeeded, path is capable"); |
|
self.state.set(ValidationState::Capable, stats); |
|
} |
|
self.baseline = ack_ecn; |
|
self.largest_acked = largest_acked.pn(); |
|
} |
Reports (rumors) at IETF that some network send ECN CE on all packets. We should disable ECN when e.g. the last 10 packets have all been marked as CE.
neqo/neqo-transport/src/ecn.rs
Lines 266 to 337 in e2a2a74