github.com/quic-go/quic-go@v0.44.0/http3/conn_test.go (about) 1 package http3 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "time" 9 10 "github.com/quic-go/quic-go" 11 mockquic "github.com/quic-go/quic-go/internal/mocks/quic" 12 "github.com/quic-go/quic-go/internal/protocol" 13 "github.com/quic-go/quic-go/internal/qerr" 14 "github.com/quic-go/quic-go/quicvarint" 15 16 . "github.com/onsi/ginkgo/v2" 17 . "github.com/onsi/gomega" 18 "go.uber.org/mock/gomock" 19 ) 20 21 var _ = Describe("Connection", func() { 22 Context("control stream handling", func() { 23 It("parses the SETTINGS frame", func() { 24 qconn := mockquic.NewMockEarlyConnection(mockCtrl) 25 qconn.EXPECT().ReceiveDatagram(gomock.Any()).Return(nil, errors.New("no datagrams")) 26 conn := newConnection( 27 qconn, 28 false, 29 protocol.PerspectiveServer, 30 nil, 31 ) 32 b := quicvarint.Append(nil, streamTypeControlStream) 33 b = (&settingsFrame{ 34 Datagram: true, 35 ExtendedConnect: true, 36 Other: map[uint64]uint64{1337: 42}, 37 }).Append(b) 38 r := bytes.NewReader(b) 39 controlStr := mockquic.NewMockStream(mockCtrl) 40 controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes() 41 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(controlStr, nil) 42 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(nil, errors.New("test done")) 43 done := make(chan struct{}) 44 go func() { 45 defer GinkgoRecover() 46 defer close(done) 47 conn.HandleUnidirectionalStreams(nil) 48 }() 49 Eventually(conn.ReceivedSettings()).Should(BeClosed()) 50 Expect(conn.Settings().EnableDatagrams).To(BeTrue()) 51 Expect(conn.Settings().EnableExtendedConnect).To(BeTrue()) 52 Expect(conn.Settings().Other).To(HaveKeyWithValue(uint64(1337), uint64(42))) 53 Eventually(done).Should(BeClosed()) 54 }) 55 56 It("rejects duplicate control streams", func() { 57 qconn := mockquic.NewMockEarlyConnection(mockCtrl) 58 conn := newConnection( 59 qconn, 60 false, 61 protocol.PerspectiveServer, 62 nil, 63 ) 64 b := quicvarint.Append(nil, streamTypeControlStream) 65 b = (&settingsFrame{}).Append(b) 66 r1 := bytes.NewReader(b) 67 controlStr1 := mockquic.NewMockStream(mockCtrl) 68 controlStr1.EXPECT().Read(gomock.Any()).DoAndReturn(r1.Read).AnyTimes() 69 r2 := bytes.NewReader(b) 70 controlStr2 := mockquic.NewMockStream(mockCtrl) 71 controlStr2.EXPECT().Read(gomock.Any()).DoAndReturn(r2.Read).AnyTimes() 72 done := make(chan struct{}) 73 closed := make(chan struct{}) 74 qconn.EXPECT().CloseWithError(qerr.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream").Do(func(qerr.ApplicationErrorCode, string) error { 75 close(closed) 76 return nil 77 }) 78 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(controlStr1, nil) 79 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(controlStr2, nil) 80 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(nil, errors.New("test done")) 81 go func() { 82 defer GinkgoRecover() 83 defer close(done) 84 conn.HandleUnidirectionalStreams(nil) 85 }() 86 Eventually(closed).Should(BeClosed()) 87 Eventually(done).Should(BeClosed()) 88 }) 89 90 for _, t := range []uint64{streamTypeQPACKEncoderStream, streamTypeQPACKDecoderStream} { 91 streamType := t 92 name := "encoder" 93 if streamType == streamTypeQPACKDecoderStream { 94 name = "decoder" 95 } 96 97 It(fmt.Sprintf("ignores the QPACK %s streams", name), func() { 98 qconn := mockquic.NewMockEarlyConnection(mockCtrl) 99 conn := newConnection( 100 qconn, 101 false, 102 protocol.PerspectiveClient, 103 nil, 104 ) 105 buf := bytes.NewBuffer(quicvarint.Append(nil, streamType)) 106 str := mockquic.NewMockStream(mockCtrl) 107 str.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes() 108 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(str, nil) 109 testDone := make(chan struct{}) 110 qconn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) { 111 <-testDone 112 return nil, errors.New("test done") 113 }) 114 time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to str.CancelRead 115 close(testDone) 116 done := make(chan struct{}) 117 go func() { 118 defer GinkgoRecover() 119 defer close(done) 120 conn.HandleUnidirectionalStreams(nil) 121 }() 122 Eventually(done).Should(BeClosed()) 123 }) 124 125 It(fmt.Sprintf("rejects duplicate QPACK %s streams", name), func() { 126 qconn := mockquic.NewMockEarlyConnection(mockCtrl) 127 conn := newConnection( 128 qconn, 129 false, 130 protocol.PerspectiveClient, 131 nil, 132 ) 133 buf := bytes.NewBuffer(quicvarint.Append(nil, streamType)) 134 str1 := mockquic.NewMockStream(mockCtrl) 135 str1.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes() 136 buf2 := bytes.NewBuffer(quicvarint.Append(nil, streamType)) 137 str2 := mockquic.NewMockStream(mockCtrl) 138 str2.EXPECT().Read(gomock.Any()).DoAndReturn(buf2.Read).AnyTimes() 139 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(str1, nil) 140 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(str2, nil) 141 testDone := make(chan struct{}) 142 qconn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) { 143 <-testDone 144 return nil, errors.New("test done") 145 }) 146 qconn.EXPECT().CloseWithError(qerr.ApplicationErrorCode(ErrCodeStreamCreationError), gomock.Any()).Do(func(qerr.ApplicationErrorCode, string) error { 147 close(testDone) 148 return nil 149 }) 150 done := make(chan struct{}) 151 go func() { 152 defer GinkgoRecover() 153 defer close(done) 154 conn.HandleUnidirectionalStreams(nil) 155 }() 156 Eventually(done).Should(BeClosed()) 157 }) 158 } 159 160 It("resets streams other than the control stream and the QPACK streams", func() { 161 qconn := mockquic.NewMockEarlyConnection(mockCtrl) 162 conn := newConnection( 163 qconn, 164 false, 165 protocol.PerspectiveServer, 166 nil, 167 ) 168 buf := bytes.NewBuffer(quicvarint.Append(nil, 0x1337)) 169 str := mockquic.NewMockStream(mockCtrl) 170 str.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes() 171 reset := make(chan struct{}) 172 str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError)).Do(func(quic.StreamErrorCode) { close(reset) }) 173 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(str, nil) 174 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(nil, errors.New("test done")) 175 done := make(chan struct{}) 176 go func() { 177 defer GinkgoRecover() 178 defer close(done) 179 conn.HandleUnidirectionalStreams(nil) 180 }() 181 Eventually(done).Should(BeClosed()) 182 Eventually(reset).Should(BeClosed()) 183 }) 184 185 It("errors when the first frame on the control stream is not a SETTINGS frame", func() { 186 qconn := mockquic.NewMockEarlyConnection(mockCtrl) 187 conn := newConnection( 188 qconn, 189 false, 190 protocol.PerspectiveServer, 191 nil, 192 ) 193 b := quicvarint.Append(nil, streamTypeControlStream) 194 b = (&dataFrame{}).Append(b) 195 r := bytes.NewReader(b) 196 controlStr := mockquic.NewMockStream(mockCtrl) 197 controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes() 198 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(controlStr, nil) 199 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(nil, errors.New("test done")) 200 closed := make(chan struct{}) 201 qconn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error { 202 close(closed) 203 return nil 204 }) 205 done := make(chan struct{}) 206 go func() { 207 defer GinkgoRecover() 208 defer close(done) 209 conn.HandleUnidirectionalStreams(nil) 210 }() 211 Eventually(done).Should(BeClosed()) 212 Eventually(closed).Should(BeClosed()) 213 }) 214 215 It("errors when parsing the frame on the control stream fails", func() { 216 qconn := mockquic.NewMockEarlyConnection(mockCtrl) 217 conn := newConnection( 218 qconn, 219 false, 220 protocol.PerspectiveServer, 221 nil, 222 ) 223 b := quicvarint.Append(nil, streamTypeControlStream) 224 b = (&settingsFrame{}).Append(b) 225 r := bytes.NewReader(b[:len(b)-1]) 226 controlStr := mockquic.NewMockStream(mockCtrl) 227 controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes() 228 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(controlStr, nil) 229 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(nil, errors.New("test done")) 230 closed := make(chan struct{}) 231 qconn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) error { 232 close(closed) 233 return nil 234 }) 235 done := make(chan struct{}) 236 go func() { 237 defer GinkgoRecover() 238 defer close(done) 239 conn.HandleUnidirectionalStreams(nil) 240 }() 241 Eventually(done).Should(BeClosed()) 242 Eventually(closed).Should(BeClosed()) 243 }) 244 245 for _, pers := range []protocol.Perspective{protocol.PerspectiveServer, protocol.PerspectiveClient} { 246 pers := pers 247 expectedErr := ErrCodeIDError 248 if pers == protocol.PerspectiveClient { 249 expectedErr = ErrCodeStreamCreationError 250 } 251 252 It(fmt.Sprintf("errors when parsing the %s opens a push stream", pers), func() { 253 qconn := mockquic.NewMockEarlyConnection(mockCtrl) 254 conn := newConnection( 255 qconn, 256 false, 257 pers.Opposite(), 258 nil, 259 ) 260 buf := bytes.NewBuffer(quicvarint.Append(nil, streamTypePushStream)) 261 controlStr := mockquic.NewMockStream(mockCtrl) 262 controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes() 263 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(controlStr, nil) 264 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(nil, errors.New("test done")) 265 closed := make(chan struct{}) 266 qconn.EXPECT().CloseWithError(quic.ApplicationErrorCode(expectedErr), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error { 267 close(closed) 268 return nil 269 }) 270 done := make(chan struct{}) 271 go func() { 272 defer GinkgoRecover() 273 defer close(done) 274 conn.HandleUnidirectionalStreams(nil) 275 }() 276 Eventually(done).Should(BeClosed()) 277 Eventually(closed).Should(BeClosed()) 278 }) 279 } 280 281 It("errors when the server advertises datagram support (and we enabled support for it)", func() { 282 qconn := mockquic.NewMockEarlyConnection(mockCtrl) 283 conn := newConnection( 284 qconn, 285 true, 286 protocol.PerspectiveClient, 287 nil, 288 ) 289 b := quicvarint.Append(nil, streamTypeControlStream) 290 b = (&settingsFrame{Datagram: true}).Append(b) 291 r := bytes.NewReader(b) 292 controlStr := mockquic.NewMockStream(mockCtrl) 293 controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes() 294 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(controlStr, nil) 295 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(nil, errors.New("test done")) 296 qconn.EXPECT().ConnectionState().Return(quic.ConnectionState{SupportsDatagrams: false}) 297 closed := make(chan struct{}) 298 qconn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeSettingsError), "missing QUIC Datagram support").Do(func(quic.ApplicationErrorCode, string) error { 299 close(closed) 300 return nil 301 }) 302 done := make(chan struct{}) 303 go func() { 304 defer GinkgoRecover() 305 defer close(done) 306 conn.HandleUnidirectionalStreams(nil) 307 }() 308 Eventually(done).Should(BeClosed()) 309 Eventually(closed).Should(BeClosed()) 310 }) 311 }) 312 313 Context("datagram handling", func() { 314 var ( 315 qconn *mockquic.MockEarlyConnection 316 conn *connection 317 ) 318 319 BeforeEach(func() { 320 qconn = mockquic.NewMockEarlyConnection(mockCtrl) 321 conn = newConnection( 322 qconn, 323 true, 324 protocol.PerspectiveClient, 325 nil, 326 ) 327 b := quicvarint.Append(nil, streamTypeControlStream) 328 b = (&settingsFrame{Datagram: true}).Append(b) 329 r := bytes.NewReader(b) 330 controlStr := mockquic.NewMockStream(mockCtrl) 331 controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes() 332 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(controlStr, nil).MaxTimes(1) 333 qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(nil, errors.New("test done")).MaxTimes(1) 334 qconn.EXPECT().ConnectionState().Return(quic.ConnectionState{SupportsDatagrams: true}).MaxTimes(1) 335 }) 336 337 It("closes the connection if it can't parse the quarter stream ID", func() { 338 qconn.EXPECT().ReceiveDatagram(gomock.Any()).Return([]byte{128}, nil) // return an invalid varint 339 done := make(chan struct{}) 340 qconn.EXPECT().CloseWithError(qerr.ApplicationErrorCode(ErrCodeDatagramError), gomock.Any()).Do(func(qerr.ApplicationErrorCode, string) error { 341 close(done) 342 return nil 343 }) 344 go func() { 345 defer GinkgoRecover() 346 conn.HandleUnidirectionalStreams(nil) 347 }() 348 Eventually(done).Should(BeClosed()) 349 }) 350 351 It("closes the connection if the quarter stream ID is invalid", func() { 352 b := quicvarint.Append([]byte{}, maxQuarterStreamID+1) 353 qconn.EXPECT().ReceiveDatagram(gomock.Any()).Return(b, nil) 354 done := make(chan struct{}) 355 qconn.EXPECT().CloseWithError(qerr.ApplicationErrorCode(ErrCodeDatagramError), gomock.Any()).Do(func(qerr.ApplicationErrorCode, string) error { 356 close(done) 357 return nil 358 }) 359 go func() { 360 defer GinkgoRecover() 361 conn.HandleUnidirectionalStreams(nil) 362 }() 363 Eventually(done).Should(BeClosed()) 364 }) 365 366 It("drops datagrams for non-existent streams", func() { 367 const strID = 4 368 // first deliver the datagram... 369 b := quicvarint.Append([]byte{}, strID/4) 370 b = append(b, []byte("foobar")...) 371 delivered := make(chan struct{}) 372 qconn.EXPECT().ReceiveDatagram(gomock.Any()).DoAndReturn(func(context.Context) ([]byte, error) { 373 close(delivered) 374 return b, nil 375 }) 376 go func() { 377 defer GinkgoRecover() 378 conn.HandleUnidirectionalStreams(nil) 379 }() 380 Eventually(delivered).Should(BeClosed()) 381 382 // ... then open the stream 383 qstr := mockquic.NewMockStream(mockCtrl) 384 qstr.EXPECT().StreamID().Return(strID).MinTimes(1) 385 qstr.EXPECT().Context().Return(context.Background()).AnyTimes() 386 qconn.EXPECT().OpenStreamSync(gomock.Any()).Return(qstr, nil) 387 str, err := conn.openRequestStream(context.Background(), nil, nil, true, 1000) 388 Expect(err).ToNot(HaveOccurred()) 389 ctx, cancel := context.WithCancel(context.Background()) 390 cancel() 391 _, err = str.ReceiveDatagram(ctx) 392 Expect(err).To(MatchError(context.Canceled)) 393 }) 394 395 It("delivers datagrams for existing streams", func() { 396 const strID = 4 397 398 // first open the stream... 399 qstr := mockquic.NewMockStream(mockCtrl) 400 qstr.EXPECT().StreamID().Return(strID).MinTimes(1) 401 qstr.EXPECT().Context().Return(context.Background()).AnyTimes() 402 qconn.EXPECT().OpenStreamSync(gomock.Any()).Return(qstr, nil) 403 str, err := conn.openRequestStream(context.Background(), nil, nil, true, 1000) 404 Expect(err).ToNot(HaveOccurred()) 405 406 // ... then deliver the datagram 407 b := quicvarint.Append([]byte{}, strID/4) 408 b = append(b, []byte("foobar")...) 409 qconn.EXPECT().ReceiveDatagram(gomock.Any()).Return(b, nil) 410 qconn.EXPECT().ReceiveDatagram(gomock.Any()).Return(nil, errors.New("test done")) 411 go func() { 412 defer GinkgoRecover() 413 conn.HandleUnidirectionalStreams(nil) 414 }() 415 416 data, err := str.ReceiveDatagram(context.Background()) 417 Expect(err).ToNot(HaveOccurred()) 418 Expect(data).To(Equal([]byte("foobar"))) 419 }) 420 421 It("sends datagrams", func() { 422 const strID = 404 423 expected := quicvarint.Append([]byte{}, strID/4) 424 expected = append(expected, []byte("foobar")...) 425 testErr := errors.New("test error") 426 qconn.EXPECT().SendDatagram(expected).Return(testErr) 427 428 Expect(conn.sendDatagram(strID, []byte("foobar"))).To(MatchError(testErr)) 429 }) 430 }) 431 })