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 }