github.com/metacubex/quic-go@v0.44.1-0.20240520163451-20b689a59136/internal/ackhandler/ecn.go (about)

     1  package ackhandler
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/metacubex/quic-go/internal/protocol"
     7  	"github.com/metacubex/quic-go/internal/utils"
     8  	"github.com/metacubex/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  }