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  })