github.com/sagernet/quic-go@v0.43.1-beta.1/internal/ackhandler/ecn.go (about) 1 package ackhandler 2 3 import ( 4 "fmt" 5 6 "github.com/sagernet/quic-go/internal/protocol" 7 "github.com/sagernet/quic-go/internal/utils" 8 "github.com/sagernet/quic-go/logging" 9 ) 10 11 type ecnState uint8 12 13 const ( 14 ecnStateInitial ecnState = iota 15 ecnStateTesting 16 ecnStateUnknown 17 ecnStateCapable 18 ecnStateFailed 19 ) 20 21 // must fit into an uint8, otherwise numSentTesting and numLostTesting must have a larger type 22 const numECNTestingPackets = 10 23 24 type ecnHandler interface { 25 SentPacket(protocol.PacketNumber, protocol.ECN) 26 Mode() protocol.ECN 27 HandleNewlyAcked(packets []*packet, ect0, ect1, ecnce int64) (congested bool) 28 LostPacket(protocol.PacketNumber) 29 } 30 31 // The ecnTracker performs ECN validation of a path. 32 // Once failed, it doesn't do any re-validation of the path. 33 // It is designed only work for 1-RTT packets, it doesn't handle multiple packet number spaces. 34 // In order to avoid revealing any internal state to on-path observers, 35 // callers should make sure to start using ECN (i.e. calling Mode) for the very first 1-RTT packet sent. 36 // The validation logic implemented here strictly follows the algorithm described in RFC 9000 section 13.4.2 and A.4. 37 type ecnTracker struct { 38 state ecnState 39 numSentTesting, numLostTesting uint8 40 41 firstTestingPacket protocol.PacketNumber 42 lastTestingPacket protocol.PacketNumber 43 firstCapablePacket protocol.PacketNumber 44 45 numSentECT0, numSentECT1 int64 46 numAckedECT0, numAckedECT1, numAckedECNCE int64 47 48 tracer *logging.ConnectionTracer 49 logger utils.Logger 50 } 51 52 var _ ecnHandler = &ecnTracker{} 53 54 func newECNTracker(logger utils.Logger, tracer *logging.ConnectionTracer) *ecnTracker { 55 return &ecnTracker{ 56 firstTestingPacket: protocol.InvalidPacketNumber, 57 lastTestingPacket: protocol.InvalidPacketNumber, 58 firstCapablePacket: protocol.InvalidPacketNumber, 59 state: ecnStateInitial, 60 logger: logger, 61 tracer: tracer, 62 } 63 } 64 65 func (e *ecnTracker) SentPacket(pn protocol.PacketNumber, ecn protocol.ECN) { 66 //nolint:exhaustive // These are the only ones we need to take care of. 67 switch ecn { 68 case protocol.ECNNon: 69 return 70 case protocol.ECT0: 71 e.numSentECT0++ 72 case protocol.ECT1: 73 e.numSentECT1++ 74 case protocol.ECNUnsupported: 75 if e.state != ecnStateFailed { 76 panic("didn't expect ECN to be unsupported") 77 } 78 default: 79 panic(fmt.Sprintf("sent packet with unexpected ECN marking: %s", ecn)) 80 } 81 82 if e.state == ecnStateCapable && e.firstCapablePacket == protocol.InvalidPacketNumber { 83 e.firstCapablePacket = pn 84 } 85 86 if e.state != ecnStateTesting { 87 return 88 } 89 90 e.numSentTesting++ 91 if e.firstTestingPacket == protocol.InvalidPacketNumber { 92 e.firstTestingPacket = pn 93 } 94 if e.numSentECT0+e.numSentECT1 >= numECNTestingPackets { 95 if e.tracer != nil && e.tracer.ECNStateUpdated != nil { 96 e.tracer.ECNStateUpdated(logging.ECNStateUnknown, logging.ECNTriggerNoTrigger) 97 } 98 e.state = ecnStateUnknown 99 e.lastTestingPacket = pn 100 } 101 } 102 103 func (e *ecnTracker) Mode() protocol.ECN { 104 switch e.state { 105 case ecnStateInitial: 106 if e.tracer != nil && e.tracer.ECNStateUpdated != nil { 107 e.tracer.ECNStateUpdated(logging.ECNStateTesting, logging.ECNTriggerNoTrigger) 108 } 109 e.state = ecnStateTesting 110 return e.Mode() 111 case ecnStateTesting, ecnStateCapable: 112 return protocol.ECT0 113 case ecnStateUnknown, ecnStateFailed: 114 return protocol.ECNNon 115 default: 116 panic(fmt.Sprintf("unknown ECN state: %d", e.state)) 117 } 118 } 119 120 func (e *ecnTracker) LostPacket(pn protocol.PacketNumber) { 121 if e.state != ecnStateTesting && e.state != ecnStateUnknown { 122 return 123 } 124 if !e.isTestingPacket(pn) { 125 return 126 } 127 e.numLostTesting++ 128 // Only proceed if we have sent all 10 testing packets. 129 if e.state != ecnStateUnknown { 130 return 131 } 132 if e.numLostTesting >= e.numSentTesting { 133 e.logger.Debugf("Disabling ECN. All testing packets were lost.") 134 if e.tracer != nil && e.tracer.ECNStateUpdated != nil { 135 e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedLostAllTestingPackets) 136 } 137 e.state = ecnStateFailed 138 return 139 } 140 // Path validation also fails if some testing packets are lost, and all other testing packets where CE-marked 141 e.failIfMangled() 142 } 143 144 // HandleNewlyAcked handles the ECN counts on an ACK frame. 145 // It must only be called for ACK frames that increase the largest acknowledged packet number, 146 // see section 13.4.2.1 of RFC 9000. 147 func (e *ecnTracker) HandleNewlyAcked(packets []*packet, ect0, ect1, ecnce int64) (congested bool) { 148 if e.state == ecnStateFailed { 149 return false 150 } 151 152 // ECN validation can fail if the received total count for either ECT(0) or ECT(1) exceeds 153 // the total number of packets sent with each corresponding ECT codepoint. 154 if ect0 > e.numSentECT0 || ect1 > e.numSentECT1 { 155 e.logger.Debugf("Disabling ECN. Received more ECT(0) / ECT(1) acknowledgements than packets sent.") 156 if e.tracer != nil && e.tracer.ECNStateUpdated != nil { 157 e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedMoreECNCountsThanSent) 158 } 159 e.state = ecnStateFailed 160 return false 161 } 162 163 // Count ECT0 and ECT1 marks that we used when sending the packets that are now being acknowledged. 164 var ackedECT0, ackedECT1 int64 165 for _, p := range packets { 166 //nolint:exhaustive // We only ever send ECT(0) and ECT(1). 167 switch e.ecnMarking(p.PacketNumber) { 168 case protocol.ECT0: 169 ackedECT0++ 170 case protocol.ECT1: 171 ackedECT1++ 172 } 173 } 174 175 // If an ACK frame newly acknowledges a packet that the endpoint sent with either the ECT(0) or ECT(1) 176 // codepoint set, ECN validation fails if the corresponding ECN counts are not present in the ACK frame. 177 // This check detects: 178 // * paths that bleach all ECN marks, and 179 // * peers that don't report any ECN counts 180 if (ackedECT0 > 0 || ackedECT1 > 0) && ect0 == 0 && ect1 == 0 && ecnce == 0 { 181 e.logger.Debugf("Disabling ECN. ECN-marked packet acknowledged, but no ECN counts on ACK frame.") 182 if e.tracer != nil && e.tracer.ECNStateUpdated != nil { 183 e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedNoECNCounts) 184 } 185 e.state = ecnStateFailed 186 return false 187 } 188 189 // Determine the increase in ECT0, ECT1 and ECNCE marks 190 newECT0 := ect0 - e.numAckedECT0 191 newECT1 := ect1 - e.numAckedECT1 192 newECNCE := ecnce - e.numAckedECNCE 193 194 // We're only processing ACKs that increase the Largest Acked. 195 // Therefore, the ECN counters should only ever increase. 196 // Any decrease means that the peer's counting logic is broken. 197 if newECT0 < 0 || newECT1 < 0 || newECNCE < 0 { 198 e.logger.Debugf("Disabling ECN. ECN counts decreased unexpectedly.") 199 if e.tracer != nil && e.tracer.ECNStateUpdated != nil { 200 e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedDecreasedECNCounts) 201 } 202 e.state = ecnStateFailed 203 return false 204 } 205 206 // ECN validation also fails if the sum of the increase in ECT(0) and ECN-CE counts is less than the number 207 // of newly acknowledged packets that were originally sent with an ECT(0) marking. 208 // This could be the result of (partial) bleaching. 209 if newECT0+newECNCE < ackedECT0 { 210 e.logger.Debugf("Disabling ECN. Received less ECT(0) + ECN-CE than packets sent with ECT(0).") 211 if e.tracer != nil && e.tracer.ECNStateUpdated != nil { 212 e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedTooFewECNCounts) 213 } 214 e.state = ecnStateFailed 215 return false 216 } 217 // Similarly, ECN validation fails if the sum of the increases to ECT(1) and ECN-CE counts is less than 218 // the number of newly acknowledged packets sent with an ECT(1) marking. 219 if newECT1+newECNCE < ackedECT1 { 220 e.logger.Debugf("Disabling ECN. Received less ECT(1) + ECN-CE than packets sent with ECT(1).") 221 if e.tracer != nil && e.tracer.ECNStateUpdated != nil { 222 e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedTooFewECNCounts) 223 } 224 e.state = ecnStateFailed 225 return false 226 } 227 228 // update our counters 229 e.numAckedECT0 = ect0 230 e.numAckedECT1 = ect1 231 e.numAckedECNCE = ecnce 232 233 // Detect mangling (a path remarking all ECN-marked testing packets as CE), 234 // once all 10 testing packets have been sent out. 235 if e.state == ecnStateUnknown { 236 e.failIfMangled() 237 if e.state == ecnStateFailed { 238 return false 239 } 240 } 241 if e.state == ecnStateTesting || e.state == ecnStateUnknown { 242 var ackedTestingPacket bool 243 for _, p := range packets { 244 if e.isTestingPacket(p.PacketNumber) { 245 ackedTestingPacket = true 246 break 247 } 248 } 249 // This check won't succeed if the path is mangling ECN-marks (i.e. rewrites all ECN-marked packets to CE). 250 if ackedTestingPacket && (newECT0 > 0 || newECT1 > 0) { 251 e.logger.Debugf("ECN capability confirmed.") 252 if e.tracer != nil && e.tracer.ECNStateUpdated != nil { 253 e.tracer.ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger) 254 } 255 e.state = ecnStateCapable 256 } 257 } 258 259 // Don't trust CE marks before having confirmed ECN capability of the path. 260 // Otherwise, mangling would be misinterpreted as actual congestion. 261 return e.state == ecnStateCapable && newECNCE > 0 262 } 263 264 // failIfMangled fails ECN validation if all testing packets are lost or CE-marked. 265 func (e *ecnTracker) failIfMangled() { 266 numAckedECNCE := e.numAckedECNCE + int64(e.numLostTesting) 267 if e.numSentECT0+e.numSentECT1 > numAckedECNCE { 268 return 269 } 270 if e.tracer != nil && e.tracer.ECNStateUpdated != nil { 271 e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedManglingDetected) 272 } 273 e.state = ecnStateFailed 274 } 275 276 func (e *ecnTracker) ecnMarking(pn protocol.PacketNumber) protocol.ECN { 277 if pn < e.firstTestingPacket || e.firstTestingPacket == protocol.InvalidPacketNumber { 278 return protocol.ECNNon 279 } 280 if pn < e.lastTestingPacket || e.lastTestingPacket == protocol.InvalidPacketNumber { 281 return protocol.ECT0 282 } 283 if pn < e.firstCapablePacket || e.firstCapablePacket == protocol.InvalidPacketNumber { 284 return protocol.ECNNon 285 } 286 // We don't need to deal with the case when ECN validation fails, 287 // since we're ignoring any ECN counts reported in ACK frames in that case. 288 return protocol.ECT0 289 } 290 291 func (e *ecnTracker) isTestingPacket(pn protocol.PacketNumber) bool { 292 if e.firstTestingPacket == protocol.InvalidPacketNumber { 293 return false 294 } 295 return pn >= e.firstTestingPacket && (pn <= e.lastTestingPacket || e.lastTestingPacket == protocol.InvalidPacketNumber) 296 }