github.com/danielpfeifer02/quic-go-prio-packs@v0.41.0-28/conn_id_manager.go (about)

     1  package quic
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/danielpfeifer02/quic-go-prio-packs/internal/protocol"
     7  	"github.com/danielpfeifer02/quic-go-prio-packs/internal/qerr"
     8  	"github.com/danielpfeifer02/quic-go-prio-packs/internal/utils"
     9  	list "github.com/danielpfeifer02/quic-go-prio-packs/internal/utils/linkedlist"
    10  	"github.com/danielpfeifer02/quic-go-prio-packs/internal/wire"
    11  	"github.com/danielpfeifer02/quic-go-prio-packs/priority_setting"
    12  )
    13  
    14  type newConnID struct {
    15  	SequenceNumber      uint64
    16  	ConnectionID        protocol.ConnectionID
    17  	StatelessResetToken protocol.StatelessResetToken
    18  }
    19  
    20  type connIDManager struct {
    21  	queue list.List[newConnID]
    22  
    23  	handshakeComplete         bool
    24  	activeSequenceNumber      uint64
    25  	highestRetired            uint64
    26  	activeConnectionID        protocol.ConnectionID
    27  	activeStatelessResetToken *protocol.StatelessResetToken
    28  
    29  	// We change the connection ID after sending on average
    30  	// protocol.PacketsPerConnectionID packets. The actual value is randomized
    31  	// hide the packet loss rate from on-path observers.
    32  	rand                   utils.Rand
    33  	packetsSinceLastChange uint32
    34  	packetsPerConnectionID uint32
    35  
    36  	addStatelessResetToken    func(protocol.StatelessResetToken)
    37  	removeStatelessResetToken func(protocol.StatelessResetToken)
    38  	queueControlFrame         func(wire.Frame)
    39  }
    40  
    41  func newConnIDManager(
    42  	initialDestConnID protocol.ConnectionID,
    43  	addStatelessResetToken func(protocol.StatelessResetToken),
    44  	removeStatelessResetToken func(protocol.StatelessResetToken),
    45  	queueControlFrame func(wire.Frame),
    46  ) *connIDManager {
    47  	return &connIDManager{
    48  		activeConnectionID:        initialDestConnID,
    49  		addStatelessResetToken:    addStatelessResetToken,
    50  		removeStatelessResetToken: removeStatelessResetToken,
    51  		queueControlFrame:         queueControlFrame,
    52  	}
    53  }
    54  
    55  func (h *connIDManager) AddFromPreferredAddress(connID protocol.ConnectionID, resetToken protocol.StatelessResetToken) error {
    56  	return h.addConnectionID(1, connID, resetToken)
    57  }
    58  
    59  func (h *connIDManager) Add(f *wire.NewConnectionIDFrame) error {
    60  	if err := h.add(f); err != nil {
    61  		return err
    62  	}
    63  	if h.queue.Len() >= protocol.MaxActiveConnectionIDs {
    64  		return &qerr.TransportError{ErrorCode: qerr.ConnectionIDLimitError}
    65  	}
    66  	return nil
    67  }
    68  
    69  func (h *connIDManager) add(f *wire.NewConnectionIDFrame) error {
    70  	// If the NEW_CONNECTION_ID frame is reordered, such that its sequence number is smaller than the currently active
    71  	// connection ID or if it was already retired, send the RETIRE_CONNECTION_ID frame immediately.
    72  	if f.SequenceNumber < h.activeSequenceNumber || f.SequenceNumber < h.highestRetired {
    73  		h.queueControlFrame(&wire.RetireConnectionIDFrame{
    74  			SequenceNumber: f.SequenceNumber,
    75  		})
    76  		return nil
    77  	}
    78  
    79  	// Retire elements in the queue.
    80  	// Doesn't retire the active connection ID.
    81  	if f.RetirePriorTo > h.highestRetired {
    82  		var next *list.Element[newConnID]
    83  		for el := h.queue.Front(); el != nil; el = next {
    84  			if el.Value.SequenceNumber >= f.RetirePriorTo {
    85  				break
    86  			}
    87  			next = el.Next()
    88  			h.queueControlFrame(&wire.RetireConnectionIDFrame{
    89  				SequenceNumber: el.Value.SequenceNumber,
    90  			})
    91  			h.queue.Remove(el)
    92  		}
    93  		h.highestRetired = f.RetirePriorTo
    94  	}
    95  
    96  	if f.SequenceNumber == h.activeSequenceNumber {
    97  		return nil
    98  	}
    99  
   100  	if err := h.addConnectionID(f.SequenceNumber, f.ConnectionID, f.StatelessResetToken); err != nil {
   101  		return err
   102  	}
   103  
   104  	// Retire the active connection ID, if necessary.
   105  	if h.activeSequenceNumber < f.RetirePriorTo {
   106  		// The queue is guaranteed to have at least one element at this point.
   107  		h.updateConnectionID()
   108  	}
   109  	return nil
   110  }
   111  
   112  func (h *connIDManager) addConnectionID(seq uint64, connID protocol.ConnectionID, resetToken protocol.StatelessResetToken) error {
   113  	// insert a new element at the end
   114  	if h.queue.Len() == 0 || h.queue.Back().Value.SequenceNumber < seq {
   115  		h.queue.PushBack(newConnID{
   116  			SequenceNumber:      seq,
   117  			ConnectionID:        connID,
   118  			StatelessResetToken: resetToken,
   119  		})
   120  		return nil
   121  	}
   122  	// insert a new element somewhere in the middle
   123  	for el := h.queue.Front(); el != nil; el = el.Next() {
   124  		if el.Value.SequenceNumber == seq {
   125  			if el.Value.ConnectionID != connID {
   126  				return fmt.Errorf("received conflicting connection IDs for sequence number %d", seq)
   127  			}
   128  			if el.Value.StatelessResetToken != resetToken {
   129  				return fmt.Errorf("received conflicting stateless reset tokens for sequence number %d", seq)
   130  			}
   131  			break
   132  		}
   133  		if el.Value.SequenceNumber > seq {
   134  			h.queue.InsertBefore(newConnID{
   135  				SequenceNumber:      seq,
   136  				ConnectionID:        connID,
   137  				StatelessResetToken: resetToken,
   138  			}, el)
   139  			break
   140  		}
   141  	}
   142  	return nil
   143  }
   144  
   145  func (h *connIDManager) updateConnectionID() {
   146  	h.queueControlFrame(&wire.RetireConnectionIDFrame{
   147  		SequenceNumber: h.activeSequenceNumber,
   148  	})
   149  	h.highestRetired = max(h.highestRetired, h.activeSequenceNumber)
   150  	if h.activeStatelessResetToken != nil {
   151  		h.removeStatelessResetToken(*h.activeStatelessResetToken)
   152  	}
   153  
   154  	qf := h.queue.Front()
   155  	// fmt.Printf("Removing conn ID with prio %d\n", qf.Value.ConnectionID.Bytes()[0])
   156  	front := h.queue.Remove(qf)
   157  	h.activeSequenceNumber = front.SequenceNumber
   158  	h.activeConnectionID = front.ConnectionID
   159  	h.activeStatelessResetToken = &front.StatelessResetToken
   160  	h.packetsSinceLastChange = 0
   161  	h.packetsPerConnectionID = protocol.PacketsPerConnectionID/2 + uint32(h.rand.Int31n(protocol.PacketsPerConnectionID))
   162  	h.addStatelessResetToken(*h.activeStatelessResetToken)
   163  }
   164  
   165  func (h *connIDManager) Close() {
   166  	if h.activeStatelessResetToken != nil {
   167  		h.removeStatelessResetToken(*h.activeStatelessResetToken)
   168  	}
   169  }
   170  
   171  // is called when the server performs a Retry
   172  // and when the server changes the connection ID in the first Initial sent
   173  func (h *connIDManager) ChangeInitialConnID(newConnID protocol.ConnectionID) {
   174  	if h.activeSequenceNumber != 0 {
   175  		panic("expected first connection ID to have sequence number 0")
   176  	}
   177  	h.activeConnectionID = newConnID
   178  }
   179  
   180  // is called when the server provides a stateless reset token in the transport parameters
   181  func (h *connIDManager) SetStatelessResetToken(token protocol.StatelessResetToken) {
   182  	if h.activeSequenceNumber != 0 {
   183  		panic("expected first connection ID to have sequence number 0")
   184  	}
   185  	h.activeStatelessResetToken = &token
   186  	h.addStatelessResetToken(token)
   187  }
   188  
   189  func (h *connIDManager) SentPacket() {
   190  	h.packetsSinceLastChange++
   191  }
   192  
   193  func (h *connIDManager) shouldUpdateConnID() bool {
   194  	if !h.handshakeComplete {
   195  		return false
   196  	}
   197  	// initiate the first change as early as possible (after handshake completion)
   198  	if h.queue.Len() > 0 && h.activeSequenceNumber == 0 {
   199  		return true
   200  	}
   201  	// For later changes, only change if
   202  	// 1. The queue of connection IDs is filled more than 50%.
   203  	// 2. We sent at least PacketsPerConnectionID packets
   204  	// 3. The current connection ID is not the only one with the priority of the current connection ID
   205  	currentPriority := Priority(h.activeConnectionID.Bytes()[0])
   206  	onlyIDOfPriority := true
   207  	for el := h.queue.Front(); el != nil; el = el.Next() {
   208  		if el.Value.ConnectionID.Bytes()[0] == byte(currentPriority) {
   209  			onlyIDOfPriority = false
   210  			break
   211  		}
   212  	}
   213  	// fmt.Printf("Trying to remove priority %d, onlyIDOfPriority: %t\n", currentPriority, onlyIDOfPriority)
   214  	return 2*h.queue.Len() >= protocol.MaxActiveConnectionIDs &&
   215  		h.packetsSinceLastChange >= h.packetsPerConnectionID &&
   216  		!onlyIDOfPriority
   217  }
   218  
   219  // PRIO_PACKS_TAG
   220  // To keep old functionality:
   221  // prio == priority_setting.NoPriority means user does not care about priority
   222  // prio == priority_setting.LowPriority means user wants to switch to low priority connection ID
   223  // prio == priority_setting.HighPriority means user wants to switch to high priority connection ID
   224  func (h *connIDManager) Get(prio Priority) protocol.ConnectionID {
   225  	if h.shouldUpdateConnID() {
   226  		h.updateConnectionID()
   227  	}
   228  	// TODO: can this also be done before checking "shouldUpdateConnID"?
   229  	// (regarding edge case in the beginning with id == 0)
   230  	if prio != priority_setting.NoPriority {
   231  		h.SwitchToPriorityID(prio)
   232  	}
   233  	return h.activeConnectionID
   234  }
   235  
   236  func (h *connIDManager) SetHandshakeComplete() {
   237  	h.handshakeComplete = true
   238  }
   239  
   240  // PRIO_PACKS_TAG
   241  func (h *connIDManager) SwitchToPriorityID(prio Priority) {
   242  	currentConnId := h.activeConnectionID
   243  	if currentConnId.Bytes()[0] == byte(prio) || h.queue.Len() == 0 {
   244  		return
   245  	}
   246  
   247  	for i := 0; i < h.queue.Len(); i++ {
   248  		// save the current state
   249  		oldConnID := currentConnId
   250  		oldSeq := h.activeSequenceNumber
   251  		oldResetToken := *h.activeStatelessResetToken
   252  
   253  		// get the next potential state
   254  		potential := h.queue.Front().Value
   255  		h.queue.Remove(h.queue.Front())
   256  
   257  		h.queue.PushBack(newConnID{
   258  			SequenceNumber:      oldSeq,
   259  			ConnectionID:        oldConnID,
   260  			StatelessResetToken: oldResetToken,
   261  		})
   262  
   263  		// if the priority matches, switch to that state
   264  		// otherwise push current one back again and try the next one
   265  		if potential.ConnectionID.Bytes()[0] == byte(prio) {
   266  			h.activeConnectionID = potential.ConnectionID
   267  			h.activeSequenceNumber = potential.SequenceNumber
   268  			h.activeStatelessResetToken = &potential.StatelessResetToken
   269  			h.packetsSinceLastChange = 0
   270  			return
   271  		}
   272  	}
   273  
   274  	fmt.Println("No connection ID with the requested priority found. (%d)", prio)
   275  }