mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-04-03 04:07:35 +03:00
sync: quic-go 0.42.0
Signed-off-by: Gaukas Wang <i@gaukas.wang>
This commit is contained in:
parent
d40dde9b9b
commit
4973374ea5
252 changed files with 13121 additions and 5437 deletions
104
http3/README.md
Normal file
104
http3/README.md
Normal file
|
@ -0,0 +1,104 @@
|
|||
# HTTP/3
|
||||
|
||||
[](https://pkg.go.dev/github.com/quic-go/quic-go/http3)
|
||||
|
||||
This package implements HTTP/3 ([RFC 9114](https://datatracker.ietf.org/doc/html/rfc9114)), including QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)).
|
||||
It aims to provide feature parity with the standard library's HTTP/1.1 and HTTP/2 implementation.
|
||||
|
||||
## Serving HTTP/3
|
||||
|
||||
The easiest way to start an HTTP/3 server is using
|
||||
```go
|
||||
mux := http.NewServeMux()
|
||||
// ... add HTTP handlers to mux ...
|
||||
// If mux is nil, the http.DefaultServeMux is used.
|
||||
http3.ListenAndServeQUIC("0.0.0.0:443", "/path/to/cert", "/path/to/key", mux)
|
||||
```
|
||||
|
||||
`ListenAndServeQUIC` is a convenience function. For more configurability, set up an `http3.Server` explicitly:
|
||||
```go
|
||||
server := http3.Server{
|
||||
Handler: mux,
|
||||
Addr: "0.0.0.0:443",
|
||||
TLSConfig: http3.ConfigureTLSConfig(&tls.Config{}), // use your tls.Config here
|
||||
QuicConfig: &quic.Config{},
|
||||
}
|
||||
err := server.ListenAndServe()
|
||||
```
|
||||
|
||||
The `http3.Server` provides a number of configuration options, please refer to the [documentation](https://pkg.go.dev/github.com/quic-go/quic-go/http3#Server) for a complete list. The `QuicConfig` is used to configure the underlying QUIC connection. More details can be found in the documentation of the QUIC package.
|
||||
|
||||
It is also possible to manually set up a `quic.Transport`, and then pass the listener to the server. This is useful when you want to set configuration options on the `quic.Transport`.
|
||||
```go
|
||||
tr := quic.Transport{Conn: conn}
|
||||
tlsConf := http3.ConfigureTLSConfig(&tls.Config{}) // use your tls.Config here
|
||||
quicConf := &quic.Config{} // QUIC connection options
|
||||
server := http3.Server{}
|
||||
ln, _ := tr.ListenEarly(tlsConf, quicConf)
|
||||
server.ServeListener(ln)
|
||||
```
|
||||
|
||||
Alternatively, it is also possible to pass fully established QUIC connections to the HTTP/3 server. This is useful if the QUIC server offers multiple ALPNs (via `NextProtos` in the `tls.Config`).
|
||||
```go
|
||||
tr := quic.Transport{Conn: conn}
|
||||
tlsConf := http3.ConfigureTLSConfig(&tls.Config{}) // use your tls.Config here
|
||||
quicConf := &quic.Config{} // QUIC connection options
|
||||
server := http3.Server{}
|
||||
// alternatively, use tr.ListenEarly to accept 0-RTT connections
|
||||
ln, _ := tr.Listen(tlsConf, quicConf)
|
||||
for {
|
||||
c, _ := ln.Accept()
|
||||
switch c.ConnectionState().TLS.NegotiatedProtocol {
|
||||
case http3.NextProtoH3:
|
||||
go server.ServeQUICConn(c)
|
||||
// ... handle other protocols ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dialing HTTP/3
|
||||
|
||||
This package provides a `http.RoundTripper` implementation that can be used on the `http.Client`:
|
||||
|
||||
```go
|
||||
&http3.RoundTripper{
|
||||
TLSClientConfig: &tls.Config{}, // set a TLS client config, if desired
|
||||
QuicConfig: &quic.Config{}, // QUIC connection options
|
||||
}
|
||||
defer roundTripper.Close()
|
||||
client := &http.Client{
|
||||
Transport: roundTripper,
|
||||
}
|
||||
```
|
||||
|
||||
The `http3.RoundTripper` provides a number of configuration options, please refer to the [documentation](https://pkg.go.dev/github.com/quic-go/quic-go/http3#RoundTripper) for a complete list.
|
||||
|
||||
To use a custom `quic.Transport`, the function used to dial new QUIC connections can be configured:
|
||||
```go
|
||||
tr := quic.Transport{}
|
||||
roundTripper := &http3.RoundTripper{
|
||||
TLSClientConfig: &tls.Config{}, // set a TLS client config, if desired
|
||||
QuicConfig: &quic.Config{}, // QUIC connection options
|
||||
Dial: func(ctx context.Context, addr string, tlsConf *tls.Config, quicConf *quic.Config) (quic.EarlyConnection, error) {
|
||||
a, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tr.DialEarly(ctx, a, tlsConf, quicConf)
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Using the same UDP Socket for Server and Roundtripper
|
||||
|
||||
Since QUIC demultiplexes packets based on their connection IDs, it is possible allows running a QUIC server and client on the same UDP socket. This also works when using HTTP/3: HTTP requests can be sent from the same socket that a server is listening on.
|
||||
|
||||
To achieve this using this package, first initialize a single `quic.Transport`, and pass a `quic.EarlyListner` obtained from that transport to `http3.Server.ServeListener`, and use the `DialEarly` function of the transport as the `Dial` function for the `http3.RoundTripper`.
|
||||
|
||||
## QPACK
|
||||
|
||||
HTTP/3 utilizes QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)) for efficient HTTP header field compression. Our implementation, available at[quic-go/qpack](https://github.com/quic-go/qpack), provides a minimal implementation of the protocol.
|
||||
|
||||
While the current implementation is a fully interoperable implementation of the QPACK protocol, it only uses the static compression table. The dynamic table would allow for more effective compression of frequently transmitted header fields. This can be particularly beneficial in scenarios where headers have considerable redundancy or in high-throughput environments.
|
||||
|
||||
If you think that your application would benefit from higher compression efficiency, or if you're interested in contributing improvements here, please let us know in [#2424](https://github.com/quic-go/quic-go/issues/2424).
|
|
@ -61,6 +61,9 @@ type client struct {
|
|||
dialer dialFunc
|
||||
handshakeErr error
|
||||
|
||||
receivedSettings chan struct{} // closed once the server's SETTINGS frame was processed
|
||||
settings *Settings // set once receivedSettings is closed
|
||||
|
||||
requestWriter *requestWriter
|
||||
|
||||
decoder *qpack.Decoder
|
||||
|
@ -76,10 +79,14 @@ var _ roundTripCloser = &client{}
|
|||
func newClient(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, conf *quic.Config, dialer dialFunc) (roundTripCloser, error) {
|
||||
if conf == nil {
|
||||
conf = defaultQuicConfig.Clone()
|
||||
conf.EnableDatagrams = opts.EnableDatagram
|
||||
}
|
||||
if opts.EnableDatagram && !conf.EnableDatagrams {
|
||||
return nil, errors.New("HTTP Datagrams enabled, but QUIC Datagrams disabled")
|
||||
}
|
||||
if len(conf.Versions) == 0 {
|
||||
conf = conf.Clone()
|
||||
conf.Versions = []quic.VersionNumber{protocol.SupportedVersions[0]}
|
||||
conf.Versions = []quic.Version{protocol.SupportedVersions[0]}
|
||||
}
|
||||
if len(conf.Versions) != 1 {
|
||||
return nil, errors.New("can only use a single QUIC version for dialing a HTTP/3 connection")
|
||||
|
@ -87,7 +94,6 @@ func newClient(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, con
|
|||
if conf.MaxIncomingStreams == 0 {
|
||||
conf.MaxIncomingStreams = -1 // don't allow any bidirectional streams
|
||||
}
|
||||
conf.EnableDatagrams = opts.EnableDatagram
|
||||
logger := utils.DefaultLogger.WithPrefix("h3 client")
|
||||
|
||||
if tlsConf == nil {
|
||||
|
@ -107,14 +113,15 @@ func newClient(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, con
|
|||
tlsConf.NextProtos = []string{versionToALPN(conf.Versions[0])}
|
||||
|
||||
return &client{
|
||||
hostname: authorityAddr("https", hostname),
|
||||
tlsConf: tlsConf,
|
||||
requestWriter: newRequestWriter(logger),
|
||||
decoder: qpack.NewDecoder(func(hf qpack.HeaderField) {}),
|
||||
config: conf,
|
||||
opts: opts,
|
||||
dialer: dialer,
|
||||
logger: logger,
|
||||
hostname: authorityAddr("https", hostname),
|
||||
tlsConf: tlsConf,
|
||||
requestWriter: newRequestWriter(logger),
|
||||
receivedSettings: make(chan struct{}),
|
||||
decoder: qpack.NewDecoder(func(hf qpack.HeaderField) {}),
|
||||
config: conf,
|
||||
opts: opts,
|
||||
dialer: dialer,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -183,6 +190,8 @@ func (c *client) handleBidirectionalStreams(conn quic.EarlyConnection) {
|
|||
}
|
||||
|
||||
func (c *client) handleUnidirectionalStreams(conn quic.EarlyConnection) {
|
||||
var rcvdControlStream atomic.Bool
|
||||
|
||||
for {
|
||||
str, err := conn.AcceptUniStream(context.Background())
|
||||
if err != nil {
|
||||
|
@ -217,6 +226,11 @@ func (c *client) handleUnidirectionalStreams(conn quic.EarlyConnection) {
|
|||
str.CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError))
|
||||
return
|
||||
}
|
||||
// Only a single control stream is allowed.
|
||||
if isFirstControlStr := rcvdControlStream.CompareAndSwap(false, true); !isFirstControlStr {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
|
||||
return
|
||||
}
|
||||
f, err := parseNextFrame(str, nil)
|
||||
if err != nil {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
|
||||
|
@ -227,6 +241,12 @@ func (c *client) handleUnidirectionalStreams(conn quic.EarlyConnection) {
|
|||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), "")
|
||||
return
|
||||
}
|
||||
c.settings = &Settings{
|
||||
EnableDatagram: sf.Datagram,
|
||||
EnableExtendedConnect: sf.ExtendedConnect,
|
||||
Other: sf.Other,
|
||||
}
|
||||
close(c.receivedSettings)
|
||||
if !sf.Datagram {
|
||||
return
|
||||
}
|
||||
|
@ -257,6 +277,15 @@ func (c *client) maxHeaderBytes() uint64 {
|
|||
|
||||
// RoundTripOpt executes a request and returns a response
|
||||
func (c *client) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
rsp, err := c.roundTripOpt(req, opt)
|
||||
if err != nil && req.Context().Err() != nil {
|
||||
// if the context was canceled, return the context cancellation error
|
||||
err = req.Context().Err()
|
||||
}
|
||||
return rsp, err
|
||||
}
|
||||
|
||||
func (c *client) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
if authorityAddr("https", hostnameFromRequest(req)) != c.hostname {
|
||||
return nil, fmt.Errorf("http3 client BUG: RoundTripOpt called for the wrong client (expected %s, got %s)", c.hostname, req.Host)
|
||||
}
|
||||
|
@ -283,6 +312,18 @@ func (c *client) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Respon
|
|||
}
|
||||
}
|
||||
|
||||
if opt.CheckSettings != nil {
|
||||
// wait for the server's SETTINGS frame to arrive
|
||||
select {
|
||||
case <-c.receivedSettings:
|
||||
case <-conn.Context().Done():
|
||||
return nil, context.Cause(conn.Context())
|
||||
}
|
||||
if err := opt.CheckSettings(*c.settings); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
str, err := conn.OpenStreamSync(req.Context())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -11,11 +11,12 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
quic "github.com/refraction-networking/uquic"
|
||||
tls "github.com/refraction-networking/utls"
|
||||
|
||||
quic "github.com/refraction-networking/uquic"
|
||||
mockquic "github.com/refraction-networking/uquic/internal/mocks/quic"
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/qerr"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
"github.com/refraction-networking/uquic/quicvarint"
|
||||
|
||||
|
@ -56,7 +57,7 @@ var _ = Describe("Client", func() {
|
|||
|
||||
It("rejects quic.Configs that allow multiple QUIC versions", func() {
|
||||
qconf := &quic.Config{
|
||||
Versions: []quic.VersionNumber{protocol.Version2, protocol.Version1},
|
||||
Versions: []quic.Version{protocol.Version2, protocol.Version1},
|
||||
}
|
||||
_, err := newClient("localhost:1337", nil, &roundTripperOpts{}, qconf, nil)
|
||||
Expect(err).To(MatchError("can only use a single QUIC version for dialing a HTTP/3 connection"))
|
||||
|
@ -69,7 +70,7 @@ var _ = Describe("Client", func() {
|
|||
dialAddr = func(_ context.Context, _ string, tlsConf *tls.Config, quicConf *quic.Config) (quic.EarlyConnection, error) {
|
||||
Expect(quicConf.MaxIncomingStreams).To(Equal(defaultQuicConfig.MaxIncomingStreams))
|
||||
Expect(tlsConf.NextProtos).To(Equal([]string{NextProtoH3}))
|
||||
Expect(quicConf.Versions).To(Equal([]protocol.VersionNumber{protocol.Version1}))
|
||||
Expect(quicConf.Versions).To(Equal([]protocol.Version{protocol.Version1}))
|
||||
dialAddrCalled = true
|
||||
return nil, errors.New("test done")
|
||||
}
|
||||
|
@ -214,9 +215,10 @@ var _ = Describe("Client", func() {
|
|||
testDone = make(chan struct{})
|
||||
settingsFrameWritten = make(chan struct{})
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) (int, error) {
|
||||
defer GinkgoRecover()
|
||||
close(settingsFrameWritten)
|
||||
return len(b), nil
|
||||
})
|
||||
conn = mockquic.NewMockEarlyConnection(mockCtrl)
|
||||
conn.EXPECT().OpenUniStream().Return(controlStr, nil)
|
||||
|
@ -340,9 +342,10 @@ var _ = Describe("Client", func() {
|
|||
testDone = make(chan struct{})
|
||||
settingsFrameWritten = make(chan struct{})
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) (int, error) {
|
||||
defer GinkgoRecover()
|
||||
close(settingsFrameWritten)
|
||||
return len(b), nil
|
||||
})
|
||||
conn = mockquic.NewMockEarlyConnection(mockCtrl)
|
||||
conn.EXPECT().OpenUniStream().Return(controlStr, nil)
|
||||
|
@ -441,19 +444,19 @@ var _ = Describe("Client", func() {
|
|||
conn *mockquic.MockEarlyConnection
|
||||
settingsFrameWritten chan struct{}
|
||||
)
|
||||
testDone := make(chan struct{})
|
||||
testDone := make(chan struct{}, 1)
|
||||
|
||||
BeforeEach(func() {
|
||||
settingsFrameWritten = make(chan struct{})
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) (int, error) {
|
||||
defer GinkgoRecover()
|
||||
close(settingsFrameWritten)
|
||||
return len(b), nil
|
||||
})
|
||||
conn = mockquic.NewMockEarlyConnection(mockCtrl)
|
||||
conn.EXPECT().OpenUniStream().Return(controlStr, nil)
|
||||
conn.EXPECT().HandshakeComplete().Return(handshakeChan)
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
dialAddr = func(context.Context, string, *tls.Config, *quic.Config) (quic.EarlyConnection, error) {
|
||||
return conn, nil
|
||||
}
|
||||
|
@ -469,10 +472,15 @@ var _ = Describe("Client", func() {
|
|||
|
||||
It("parses the SETTINGS frame", func() {
|
||||
b := quicvarint.Append(nil, streamTypeControlStream)
|
||||
b = (&settingsFrame{}).Append(b)
|
||||
b = (&settingsFrame{
|
||||
Datagram: true,
|
||||
ExtendedConnect: true,
|
||||
Other: map[uint64]uint64{1337: 42},
|
||||
}).Append(b)
|
||||
r := bytes.NewReader(b)
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
|
@ -480,11 +488,72 @@ var _ = Describe("Client", func() {
|
|||
<-testDone
|
||||
return nil, errors.New("test done")
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
conn.EXPECT().Context().Return(context.Background())
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{CheckSettings: func(settings Settings) error {
|
||||
defer GinkgoRecover()
|
||||
Expect(settings.EnableDatagram).To(BeTrue())
|
||||
Expect(settings.EnableExtendedConnect).To(BeTrue())
|
||||
Expect(settings.Other).To(HaveLen(1))
|
||||
Expect(settings.Other).To(HaveKeyWithValue(uint64(1337), uint64(42)))
|
||||
return nil
|
||||
}})
|
||||
Expect(err).To(MatchError("done"))
|
||||
time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to conn.CloseWithError
|
||||
})
|
||||
|
||||
It("allows the client to reject the SETTINGS using the CheckSettings RoundTripOpt", func() {
|
||||
b := quicvarint.Append(nil, streamTypeControlStream)
|
||||
b = (&settingsFrame{}).Append(b)
|
||||
r := bytes.NewReader(b)
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
// Don't EXPECT any call to OpenStreamSync.
|
||||
// When the SETTINGS are rejected, we don't even open the request stream.
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
<-testDone
|
||||
return nil, errors.New("test done")
|
||||
})
|
||||
conn.EXPECT().Context().Return(context.Background())
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{CheckSettings: func(settings Settings) error {
|
||||
return errors.New("wrong settings")
|
||||
}})
|
||||
Expect(err).To(MatchError("wrong settings"))
|
||||
time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to conn.CloseWithError
|
||||
})
|
||||
|
||||
It("rejects duplicate control streams", func() {
|
||||
b := quicvarint.Append(nil, streamTypeControlStream)
|
||||
b = (&settingsFrame{}).Append(b)
|
||||
r1 := bytes.NewReader(b)
|
||||
controlStr1 := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr1.EXPECT().Read(gomock.Any()).DoAndReturn(r1.Read).AnyTimes()
|
||||
r2 := bytes.NewReader(b)
|
||||
controlStr2 := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr2.EXPECT().Read(gomock.Any()).DoAndReturn(r2.Read).AnyTimes()
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().CloseWithError(qerr.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream").Do(func(qerr.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr1, nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr2, nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
<-done
|
||||
return nil, errors.New("test done")
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(HaveOccurred())
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
|
||||
for _, t := range []uint64{streamTypeQPACKEncoderStream, streamTypeQPACKDecoderStream} {
|
||||
streamType := t
|
||||
name := "encoder"
|
||||
|
@ -497,6 +566,7 @@ var _ = Describe("Client", func() {
|
|||
str := mockquic.NewMockStream(mockCtrl)
|
||||
str.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
|
||||
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return str, nil
|
||||
})
|
||||
|
@ -510,15 +580,14 @@ var _ = Describe("Client", func() {
|
|||
})
|
||||
}
|
||||
|
||||
It("resets streams Other than the control stream and the QPACK streams", func() {
|
||||
It("resets streams other than the control stream and the QPACK streams", func() {
|
||||
buf := bytes.NewBuffer(quicvarint.Append(nil, 0x1337))
|
||||
str := mockquic.NewMockStream(mockCtrl)
|
||||
str.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
|
||||
done := make(chan struct{})
|
||||
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError)).Do(func(code quic.StreamErrorCode) {
|
||||
close(done)
|
||||
})
|
||||
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError)).Do(func(quic.StreamErrorCode) { close(done) })
|
||||
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return str, nil
|
||||
})
|
||||
|
@ -537,6 +606,8 @@ var _ = Describe("Client", func() {
|
|||
r := bytes.NewReader(b)
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
|
@ -545,22 +616,53 @@ var _ = Describe("Client", func() {
|
|||
return nil, errors.New("test done")
|
||||
})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeMissingSettings))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("done"))
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
|
||||
It("errors when the first frame on the control stream is not a SETTINGS frame, when checking SETTINGS", func() {
|
||||
b := quicvarint.Append(nil, streamTypeControlStream)
|
||||
b = (&dataFrame{}).Append(b)
|
||||
r := bytes.NewReader(b)
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
|
||||
// Don't EXPECT any calls to OpenStreamSync.
|
||||
// We fail before we even get the chance to open the request stream.
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
<-testDone
|
||||
return nil, errors.New("test done")
|
||||
})
|
||||
doneCtx, doneCancel := context.WithCancelCause(context.Background())
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
doneCancel(errors.New("done"))
|
||||
return nil
|
||||
})
|
||||
conn.EXPECT().Context().Return(doneCtx).Times(2)
|
||||
var checked bool
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{
|
||||
CheckSettings: func(Settings) error { checked = true; return nil },
|
||||
})
|
||||
Expect(checked).To(BeFalse())
|
||||
Expect(err).To(MatchError("done"))
|
||||
Eventually(doneCtx.Done()).Should(BeClosed())
|
||||
})
|
||||
|
||||
It("errors when parsing the frame on the control stream fails", func() {
|
||||
b := quicvarint.Append(nil, streamTypeControlStream)
|
||||
b = (&settingsFrame{}).Append(b)
|
||||
r := bytes.NewReader(b[:len(b)-1])
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
|
@ -569,10 +671,9 @@ var _ = Describe("Client", func() {
|
|||
return nil, errors.New("test done")
|
||||
})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeFrameError))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("done"))
|
||||
|
@ -583,6 +684,7 @@ var _ = Describe("Client", func() {
|
|||
buf := bytes.NewBuffer(quicvarint.Append(nil, streamTypePushStream))
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
|
@ -591,10 +693,9 @@ var _ = Describe("Client", func() {
|
|||
return nil, errors.New("test done")
|
||||
})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeIDError))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("done"))
|
||||
|
@ -608,6 +709,7 @@ var _ = Describe("Client", func() {
|
|||
r := bytes.NewReader(b)
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
|
@ -617,11 +719,9 @@ var _ = Describe("Client", func() {
|
|||
})
|
||||
conn.EXPECT().ConnectionState().Return(quic.ConnectionState{SupportsDatagrams: false})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, reason string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeSettingsError))
|
||||
Expect(reason).To(Equal("missing QUIC Datagram support"))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeSettingsError), "missing QUIC Datagram support").Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("done"))
|
||||
|
@ -670,13 +770,14 @@ var _ = Describe("Client", func() {
|
|||
BeforeEach(func() {
|
||||
settingsFrameWritten = make(chan struct{})
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) (int, error) {
|
||||
defer GinkgoRecover()
|
||||
r := bytes.NewReader(b)
|
||||
streamType, err := quicvarint.Read(r)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(streamType).To(BeEquivalentTo(streamTypeControlStream))
|
||||
close(settingsFrameWritten)
|
||||
return len(b), nil
|
||||
}) // SETTINGS frame
|
||||
str = mockquic.NewMockStream(mockCtrl)
|
||||
conn = mockquic.NewMockEarlyConnection(mockCtrl)
|
||||
|
@ -778,7 +879,7 @@ var _ = Describe("Client", func() {
|
|||
It("sends a request", func() {
|
||||
done := make(chan struct{})
|
||||
gomock.InOrder(
|
||||
str.EXPECT().Close().Do(func() { close(done) }),
|
||||
str.EXPECT().Close().Do(func() error { close(done); return nil }),
|
||||
str.EXPECT().CancelWrite(gomock.Any()).MaxTimes(1), // when reading the response errors
|
||||
)
|
||||
// the response body is sent asynchronously, while already reading the response
|
||||
|
@ -832,7 +933,7 @@ var _ = Describe("Client", func() {
|
|||
return 0, errors.New("test done")
|
||||
})
|
||||
closed := make(chan struct{})
|
||||
str.EXPECT().Close().Do(func() { close(closed) })
|
||||
str.EXPECT().Close().Do(func() error { close(closed); return nil })
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("test done"))
|
||||
Eventually(closed).Should(BeClosed())
|
||||
|
@ -843,7 +944,7 @@ var _ = Describe("Client", func() {
|
|||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), gomock.Any())
|
||||
closed := make(chan struct{})
|
||||
r := bytes.NewReader(b)
|
||||
str.EXPECT().Close().Do(func() { close(closed) })
|
||||
str.EXPECT().Close().Do(func() error { close(closed); return nil })
|
||||
str.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("expected first frame to be a HEADERS frame"))
|
||||
|
@ -861,7 +962,7 @@ var _ = Describe("Client", func() {
|
|||
r := bytes.NewReader(b)
|
||||
str.EXPECT().CancelWrite(quic.StreamErrorCode(ErrCodeMessageError))
|
||||
closed := make(chan struct{})
|
||||
str.EXPECT().Close().Do(func() { close(closed) })
|
||||
str.EXPECT().Close().Do(func() error { close(closed); return nil })
|
||||
str.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
@ -873,7 +974,7 @@ var _ = Describe("Client", func() {
|
|||
r := bytes.NewReader(b)
|
||||
str.EXPECT().CancelWrite(quic.StreamErrorCode(ErrCodeFrameError))
|
||||
closed := make(chan struct{})
|
||||
str.EXPECT().Close().Do(func() { close(closed) })
|
||||
str.EXPECT().Close().Do(func() error { close(closed); return nil })
|
||||
str.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("HEADERS frame too large: 1338 bytes (max: 1337)"))
|
||||
|
@ -926,7 +1027,7 @@ var _ = Describe("Client", func() {
|
|||
return 0, errors.New("test done")
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, roundTripOpt)
|
||||
Expect(err).To(MatchError("test done"))
|
||||
Expect(err).To(MatchError(context.Canceled))
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
})
|
||||
|
|
|
@ -88,11 +88,18 @@ func (f *headersFrame) Append(b []byte) []byte {
|
|||
return quicvarint.Append(b, f.Length)
|
||||
}
|
||||
|
||||
const settingDatagram = 0x33
|
||||
const (
|
||||
// Extended CONNECT, RFC 9220
|
||||
settingExtendedConnect = 0x8
|
||||
// HTTP Datagrams, RFC 9297
|
||||
settingDatagram = 0x33
|
||||
)
|
||||
|
||||
type settingsFrame struct {
|
||||
Datagram bool
|
||||
Other map[uint64]uint64 // all settings that we don't explicitly recognize
|
||||
Datagram bool // HTTP Datagrams, RFC 9297
|
||||
ExtendedConnect bool // Extended CONNECT, RFC 9220
|
||||
|
||||
Other map[uint64]uint64 // all settings that we don't explicitly recognize
|
||||
}
|
||||
|
||||
func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
|
||||
|
@ -108,7 +115,7 @@ func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
|
|||
}
|
||||
frame := &settingsFrame{}
|
||||
b := bytes.NewReader(buf)
|
||||
var readDatagram bool
|
||||
var readDatagram, readExtendedConnect bool
|
||||
for b.Len() > 0 {
|
||||
id, err := quicvarint.Read(b)
|
||||
if err != nil { // should not happen. We allocated the whole frame already.
|
||||
|
@ -120,13 +127,22 @@ func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
|
|||
}
|
||||
|
||||
switch id {
|
||||
case settingExtendedConnect:
|
||||
if readExtendedConnect {
|
||||
return nil, fmt.Errorf("duplicate setting: %d", id)
|
||||
}
|
||||
readExtendedConnect = true
|
||||
if val != 0 && val != 1 {
|
||||
return nil, fmt.Errorf("invalid value for SETTINGS_ENABLE_CONNECT_PROTOCOL: %d", val)
|
||||
}
|
||||
frame.ExtendedConnect = val == 1
|
||||
case settingDatagram:
|
||||
if readDatagram {
|
||||
return nil, fmt.Errorf("duplicate setting: %d", id)
|
||||
}
|
||||
readDatagram = true
|
||||
if val != 0 && val != 1 {
|
||||
return nil, fmt.Errorf("invalid value for H3_DATAGRAM: %d", val)
|
||||
return nil, fmt.Errorf("invalid value for SETTINGS_H3_DATAGRAM: %d", val)
|
||||
}
|
||||
frame.Datagram = val == 1
|
||||
default:
|
||||
|
@ -151,11 +167,18 @@ func (f *settingsFrame) Append(b []byte) []byte {
|
|||
if f.Datagram {
|
||||
l += quicvarint.Len(settingDatagram) + quicvarint.Len(1)
|
||||
}
|
||||
if f.ExtendedConnect {
|
||||
l += quicvarint.Len(settingExtendedConnect) + quicvarint.Len(1)
|
||||
}
|
||||
b = quicvarint.Append(b, uint64(l))
|
||||
if f.Datagram {
|
||||
b = quicvarint.Append(b, settingDatagram)
|
||||
b = quicvarint.Append(b, 1)
|
||||
}
|
||||
if f.ExtendedConnect {
|
||||
b = quicvarint.Append(b, settingExtendedConnect)
|
||||
b = quicvarint.Append(b, 1)
|
||||
}
|
||||
for id, val := range f.Other {
|
||||
b = quicvarint.Append(b, id)
|
||||
b = quicvarint.Append(b, val)
|
||||
|
|
|
@ -127,8 +127,8 @@ var _ = Describe("Frames", func() {
|
|||
}
|
||||
})
|
||||
|
||||
Context("H3_DATAGRAM", func() {
|
||||
It("reads the H3_DATAGRAM value", func() {
|
||||
Context("HTTP Datagrams", func() {
|
||||
It("reads the SETTINGS_H3_DATAGRAM value", func() {
|
||||
settings := quicvarint.Append(nil, settingDatagram)
|
||||
settings = quicvarint.Append(settings, 1)
|
||||
data := quicvarint.Append(nil, 4) // type byte
|
||||
|
@ -141,7 +141,7 @@ var _ = Describe("Frames", func() {
|
|||
Expect(sf.Datagram).To(BeTrue())
|
||||
})
|
||||
|
||||
It("rejects duplicate H3_DATAGRAM entries", func() {
|
||||
It("rejects duplicate SETTINGS_H3_DATAGRAM entries", func() {
|
||||
settings := quicvarint.Append(nil, settingDatagram)
|
||||
settings = quicvarint.Append(settings, 1)
|
||||
settings = quicvarint.Append(settings, settingDatagram)
|
||||
|
@ -153,23 +153,67 @@ var _ = Describe("Frames", func() {
|
|||
Expect(err).To(MatchError(fmt.Sprintf("duplicate setting: %d", settingDatagram)))
|
||||
})
|
||||
|
||||
It("rejects invalid values for the H3_DATAGRAM entry", func() {
|
||||
It("rejects invalid values for the SETTINGS_H3_DATAGRAM entry", func() {
|
||||
settings := quicvarint.Append(nil, settingDatagram)
|
||||
settings = quicvarint.Append(settings, 1337)
|
||||
data := quicvarint.Append(nil, 4) // type byte
|
||||
data = quicvarint.Append(data, uint64(len(settings)))
|
||||
data = append(data, settings...)
|
||||
_, err := parseNextFrame(bytes.NewReader(data), nil)
|
||||
Expect(err).To(MatchError("invalid value for H3_DATAGRAM: 1337"))
|
||||
Expect(err).To(MatchError("invalid value for SETTINGS_H3_DATAGRAM: 1337"))
|
||||
})
|
||||
|
||||
It("writes the H3_DATAGRAM setting", func() {
|
||||
It("writes the SETTINGS_H3_DATAGRAM setting", func() {
|
||||
sf := &settingsFrame{Datagram: true}
|
||||
frame, err := parseNextFrame(bytes.NewReader(sf.Append(nil)), nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(frame).To(Equal(sf))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Extended Connect", func() {
|
||||
It("reads the SETTINGS_ENABLE_CONNECT_PROTOCOL value", func() {
|
||||
settings := quicvarint.Append(nil, settingExtendedConnect)
|
||||
settings = quicvarint.Append(settings, 1)
|
||||
data := quicvarint.Append(nil, 4) // type byte
|
||||
data = quicvarint.Append(data, uint64(len(settings)))
|
||||
data = append(data, settings...)
|
||||
f, err := parseNextFrame(bytes.NewReader(data), nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(f).To(BeAssignableToTypeOf(&settingsFrame{}))
|
||||
sf := f.(*settingsFrame)
|
||||
Expect(sf.ExtendedConnect).To(BeTrue())
|
||||
})
|
||||
|
||||
It("rejects duplicate SETTINGS_ENABLE_CONNECT_PROTOCOL entries", func() {
|
||||
settings := quicvarint.Append(nil, settingExtendedConnect)
|
||||
settings = quicvarint.Append(settings, 1)
|
||||
settings = quicvarint.Append(settings, settingExtendedConnect)
|
||||
settings = quicvarint.Append(settings, 1)
|
||||
data := quicvarint.Append(nil, 4) // type byte
|
||||
data = quicvarint.Append(data, uint64(len(settings)))
|
||||
data = append(data, settings...)
|
||||
_, err := parseNextFrame(bytes.NewReader(data), nil)
|
||||
Expect(err).To(MatchError(fmt.Sprintf("duplicate setting: %d", settingExtendedConnect)))
|
||||
})
|
||||
|
||||
It("rejects invalid values for the SETTINGS_ENABLE_CONNECT_PROTOCOL entry", func() {
|
||||
settings := quicvarint.Append(nil, settingExtendedConnect)
|
||||
settings = quicvarint.Append(settings, 1337)
|
||||
data := quicvarint.Append(nil, 4) // type byte
|
||||
data = quicvarint.Append(data, uint64(len(settings)))
|
||||
data = append(data, settings...)
|
||||
_, err := parseNextFrame(bytes.NewReader(data), nil)
|
||||
Expect(err).To(MatchError("invalid value for SETTINGS_ENABLE_CONNECT_PROTOCOL: 1337"))
|
||||
})
|
||||
|
||||
It("writes the SETTINGS_ENABLE_CONNECT_PROTOCOL setting", func() {
|
||||
sf := &settingsFrame{ExtendedConnect: true}
|
||||
frame, err := parseNextFrame(bytes.NewReader(sf.Append(nil)), nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(frame).To(Equal(sf))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Context("hijacking", func() {
|
||||
|
|
|
@ -126,9 +126,14 @@ func requestFromHeaders(headerFields []qpack.HeaderField) (*http.Request, error)
|
|||
return nil, errors.New(":path, :authority and :method must not be empty")
|
||||
}
|
||||
|
||||
if !isExtendedConnected && len(hdr.Protocol) > 0 {
|
||||
return nil, errors.New(":protocol must be empty")
|
||||
}
|
||||
|
||||
var u *url.URL
|
||||
var requestURI string
|
||||
var protocol string
|
||||
|
||||
protocol := "HTTP/3.0"
|
||||
|
||||
if isConnect {
|
||||
u = &url.URL{}
|
||||
|
@ -137,15 +142,14 @@ func requestFromHeaders(headerFields []qpack.HeaderField) (*http.Request, error)
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protocol = hdr.Protocol
|
||||
} else {
|
||||
u.Path = hdr.Path
|
||||
}
|
||||
u.Scheme = hdr.Scheme
|
||||
u.Host = hdr.Authority
|
||||
requestURI = hdr.Authority
|
||||
protocol = hdr.Protocol
|
||||
} else {
|
||||
protocol = "HTTP/3.0"
|
||||
u, err = url.ParseRequestURI(hdr.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid content length: %w", err)
|
||||
|
|
|
@ -212,6 +212,17 @@ var _ = Describe("Request", func() {
|
|||
Expect(err).To(MatchError(":path, :authority and :method must not be empty"))
|
||||
})
|
||||
|
||||
It("errors with invalid protocol", func() {
|
||||
headers := []qpack.HeaderField{
|
||||
{Name: ":path", Value: "/foo"},
|
||||
{Name: ":authority", Value: "quic.clemente.io"},
|
||||
{Name: ":method", Value: "GET"},
|
||||
{Name: ":protocol", Value: "connect-udp"},
|
||||
}
|
||||
_, err := requestFromHeaders(headers)
|
||||
Expect(err).To(MatchError(":protocol must be empty"))
|
||||
})
|
||||
|
||||
Context("regular HTTP CONNECT", func() {
|
||||
It("handles CONNECT method", func() {
|
||||
headers := []qpack.HeaderField{
|
||||
|
@ -221,6 +232,7 @@ var _ = Describe("Request", func() {
|
|||
req, err := requestFromHeaders(headers)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(req.Method).To(Equal(http.MethodConnect))
|
||||
Expect(req.Proto).To(Equal("HTTP/3.0"))
|
||||
Expect(req.RequestURI).To(Equal("quic.clemente.io"))
|
||||
})
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
|
||||
quic "github.com/refraction-networking/uquic"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
)
|
||||
|
||||
// A Stream is a HTTP/3 stream.
|
||||
|
@ -115,7 +114,7 @@ func (s *lengthLimitedStream) Read(b []byte) (int, error) {
|
|||
if err := s.checkContentLengthViolation(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := s.stream.Read(b[:utils.Min(int64(len(b)), s.contentLength-s.read)])
|
||||
n, err := s.stream.Read(b[:min(int64(len(b)), s.contentLength-s.read)])
|
||||
s.read += int64(n)
|
||||
if err := s.checkContentLengthViolation(); err != nil {
|
||||
return n, err
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package http3 -destination mock_quic_early_listener_test.go github.com/refraction-networking/uquic/http3 QUICEarlyListener
|
||||
// mockgen -typed -package http3 -destination mock_quic_early_listener_test.go github.com/quic-go/quic-go/http3 QUICEarlyListener
|
||||
//
|
||||
|
||||
// Package http3 is a generated GoMock package.
|
||||
package http3
|
||||
|
||||
|
@ -50,9 +51,33 @@ func (m *MockQUICEarlyListener) Accept(arg0 context.Context) (quic.EarlyConnecti
|
|||
}
|
||||
|
||||
// Accept indicates an expected call of Accept.
|
||||
func (mr *MockQUICEarlyListenerMockRecorder) Accept(arg0 any) *gomock.Call {
|
||||
func (mr *MockQUICEarlyListenerMockRecorder) Accept(arg0 any) *MockQUICEarlyListenerAcceptCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockQUICEarlyListener)(nil).Accept), arg0)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockQUICEarlyListener)(nil).Accept), arg0)
|
||||
return &MockQUICEarlyListenerAcceptCall{Call: call}
|
||||
}
|
||||
|
||||
// MockQUICEarlyListenerAcceptCall wrap *gomock.Call
|
||||
type MockQUICEarlyListenerAcceptCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockQUICEarlyListenerAcceptCall) Return(arg0 quic.EarlyConnection, arg1 error) *MockQUICEarlyListenerAcceptCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockQUICEarlyListenerAcceptCall) Do(f func(context.Context) (quic.EarlyConnection, error)) *MockQUICEarlyListenerAcceptCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockQUICEarlyListenerAcceptCall) DoAndReturn(f func(context.Context) (quic.EarlyConnection, error)) *MockQUICEarlyListenerAcceptCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Addr mocks base method.
|
||||
|
@ -64,9 +89,33 @@ func (m *MockQUICEarlyListener) Addr() net.Addr {
|
|||
}
|
||||
|
||||
// Addr indicates an expected call of Addr.
|
||||
func (mr *MockQUICEarlyListenerMockRecorder) Addr() *gomock.Call {
|
||||
func (mr *MockQUICEarlyListenerMockRecorder) Addr() *MockQUICEarlyListenerAddrCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addr", reflect.TypeOf((*MockQUICEarlyListener)(nil).Addr))
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addr", reflect.TypeOf((*MockQUICEarlyListener)(nil).Addr))
|
||||
return &MockQUICEarlyListenerAddrCall{Call: call}
|
||||
}
|
||||
|
||||
// MockQUICEarlyListenerAddrCall wrap *gomock.Call
|
||||
type MockQUICEarlyListenerAddrCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockQUICEarlyListenerAddrCall) Return(arg0 net.Addr) *MockQUICEarlyListenerAddrCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockQUICEarlyListenerAddrCall) Do(f func() net.Addr) *MockQUICEarlyListenerAddrCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockQUICEarlyListenerAddrCall) DoAndReturn(f func() net.Addr) *MockQUICEarlyListenerAddrCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
|
@ -78,7 +127,31 @@ func (m *MockQUICEarlyListener) Close() error {
|
|||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockQUICEarlyListenerMockRecorder) Close() *gomock.Call {
|
||||
func (mr *MockQUICEarlyListenerMockRecorder) Close() *MockQUICEarlyListenerCloseCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockQUICEarlyListener)(nil).Close))
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockQUICEarlyListener)(nil).Close))
|
||||
return &MockQUICEarlyListenerCloseCall{Call: call}
|
||||
}
|
||||
|
||||
// MockQUICEarlyListenerCloseCall wrap *gomock.Call
|
||||
type MockQUICEarlyListenerCloseCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockQUICEarlyListenerCloseCall) Return(arg0 error) *MockQUICEarlyListenerCloseCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockQUICEarlyListenerCloseCall) Do(f func() error) *MockQUICEarlyListenerCloseCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockQUICEarlyListenerCloseCall) DoAndReturn(f func() error) *MockQUICEarlyListenerCloseCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -build_flags=-tags=gomock -package http3 -destination mock_roundtripcloser_test.go github.com/refraction-networking/uquic/http3 RoundTripCloser
|
||||
// mockgen -typed -build_flags=-tags=gomock -package http3 -destination mock_roundtripcloser_test.go github.com/quic-go/quic-go/http3 RoundTripCloser
|
||||
//
|
||||
|
||||
// Package http3 is a generated GoMock package.
|
||||
package http3
|
||||
|
||||
|
@ -47,9 +48,33 @@ func (m *MockRoundTripCloser) Close() error {
|
|||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockRoundTripCloserMockRecorder) Close() *gomock.Call {
|
||||
func (mr *MockRoundTripCloserMockRecorder) Close() *MockRoundTripCloserCloseCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockRoundTripCloser)(nil).Close))
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockRoundTripCloser)(nil).Close))
|
||||
return &MockRoundTripCloserCloseCall{Call: call}
|
||||
}
|
||||
|
||||
// MockRoundTripCloserCloseCall wrap *gomock.Call
|
||||
type MockRoundTripCloserCloseCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockRoundTripCloserCloseCall) Return(arg0 error) *MockRoundTripCloserCloseCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockRoundTripCloserCloseCall) Do(f func() error) *MockRoundTripCloserCloseCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockRoundTripCloserCloseCall) DoAndReturn(f func() error) *MockRoundTripCloserCloseCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// HandshakeComplete mocks base method.
|
||||
|
@ -61,9 +86,33 @@ func (m *MockRoundTripCloser) HandshakeComplete() bool {
|
|||
}
|
||||
|
||||
// HandshakeComplete indicates an expected call of HandshakeComplete.
|
||||
func (mr *MockRoundTripCloserMockRecorder) HandshakeComplete() *gomock.Call {
|
||||
func (mr *MockRoundTripCloserMockRecorder) HandshakeComplete() *MockRoundTripCloserHandshakeCompleteCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandshakeComplete", reflect.TypeOf((*MockRoundTripCloser)(nil).HandshakeComplete))
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandshakeComplete", reflect.TypeOf((*MockRoundTripCloser)(nil).HandshakeComplete))
|
||||
return &MockRoundTripCloserHandshakeCompleteCall{Call: call}
|
||||
}
|
||||
|
||||
// MockRoundTripCloserHandshakeCompleteCall wrap *gomock.Call
|
||||
type MockRoundTripCloserHandshakeCompleteCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockRoundTripCloserHandshakeCompleteCall) Return(arg0 bool) *MockRoundTripCloserHandshakeCompleteCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockRoundTripCloserHandshakeCompleteCall) Do(f func() bool) *MockRoundTripCloserHandshakeCompleteCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockRoundTripCloserHandshakeCompleteCall) DoAndReturn(f func() bool) *MockRoundTripCloserHandshakeCompleteCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// RoundTripOpt mocks base method.
|
||||
|
@ -76,7 +125,31 @@ func (m *MockRoundTripCloser) RoundTripOpt(arg0 *http.Request, arg1 RoundTripOpt
|
|||
}
|
||||
|
||||
// RoundTripOpt indicates an expected call of RoundTripOpt.
|
||||
func (mr *MockRoundTripCloserMockRecorder) RoundTripOpt(arg0, arg1 any) *gomock.Call {
|
||||
func (mr *MockRoundTripCloserMockRecorder) RoundTripOpt(arg0, arg1 any) *MockRoundTripCloserRoundTripOptCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RoundTripOpt", reflect.TypeOf((*MockRoundTripCloser)(nil).RoundTripOpt), arg0, arg1)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RoundTripOpt", reflect.TypeOf((*MockRoundTripCloser)(nil).RoundTripOpt), arg0, arg1)
|
||||
return &MockRoundTripCloserRoundTripOptCall{Call: call}
|
||||
}
|
||||
|
||||
// MockRoundTripCloserRoundTripOptCall wrap *gomock.Call
|
||||
type MockRoundTripCloserRoundTripOptCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockRoundTripCloserRoundTripOptCall) Return(arg0 *http.Response, arg1 error) *MockRoundTripCloserRoundTripOptCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockRoundTripCloserRoundTripOptCall) Do(f func(*http.Request, RoundTripOpt) (*http.Response, error)) *MockRoundTripCloserRoundTripOptCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockRoundTripCloserRoundTripOptCall) DoAndReturn(f func(*http.Request, RoundTripOpt) (*http.Response, error)) *MockRoundTripCloserRoundTripOptCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
package http3
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -build_flags=\"-tags=gomock\" -package http3 -destination mock_roundtripcloser_test.go github.com/refraction-networking/uquic/http3 RoundTripCloser"
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package http3 -destination mock_roundtripcloser_test.go github.com/quic-go/quic-go/http3 RoundTripCloser"
|
||||
type RoundTripCloser = roundTripCloser
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -package http3 -destination mock_quic_early_listener_test.go github.com/refraction-networking/uquic/http3 QUICEarlyListener"
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -package http3 -destination mock_quic_early_listener_test.go github.com/quic-go/quic-go/http3 QUICEarlyListener"
|
||||
|
|
|
@ -67,9 +67,10 @@ type responseWriter struct {
|
|||
bufferedStr *bufio.Writer
|
||||
buf []byte
|
||||
|
||||
headerWritten bool
|
||||
contentLen int64 // if handler set valid Content-Length header
|
||||
numWritten int64 // bytes written
|
||||
headerWritten bool
|
||||
isHead bool
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -162,6 +163,10 @@ func (w *responseWriter) Write(p []byte) (int, error) {
|
|||
return 0, http.ErrContentLength
|
||||
}
|
||||
|
||||
if w.isHead {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
df := &dataFrame{Length: uint64(len(p))}
|
||||
w.buf = w.buf[:0]
|
||||
w.buf = df.Append(w.buf)
|
||||
|
|
|
@ -17,6 +17,30 @@ import (
|
|||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
// Settings are HTTP/3 settings that apply to the underlying connection.
|
||||
type Settings struct {
|
||||
// Support for HTTP/3 datagrams (RFC 9297)
|
||||
EnableDatagram bool
|
||||
// Extended CONNECT, RFC 9220
|
||||
EnableExtendedConnect bool
|
||||
// Other settings, defined by the application
|
||||
Other map[uint64]uint64
|
||||
}
|
||||
|
||||
// RoundTripOpt are options for the Transport.RoundTripOpt method.
|
||||
type RoundTripOpt struct {
|
||||
// OnlyCachedConn controls whether the RoundTripper may create a new QUIC connection.
|
||||
// If set true and no cached connection is available, RoundTripOpt will return ErrNoCachedConn.
|
||||
OnlyCachedConn bool
|
||||
// DontCloseRequestStream controls whether the request stream is closed after sending the request.
|
||||
// If set, context cancellations have no effect after the response headers are received.
|
||||
DontCloseRequestStream bool
|
||||
// CheckSettings is run before the request is sent to the server.
|
||||
// If not yet received, it blocks until the server's SETTINGS frame is received.
|
||||
// If an error is returned, the request won't be sent to the server, and the error is returned.
|
||||
CheckSettings func(Settings) error
|
||||
}
|
||||
|
||||
type roundTripCloser interface {
|
||||
RoundTripOpt(*http.Request, RoundTripOpt) (*http.Response, error)
|
||||
HandshakeComplete() bool
|
||||
|
@ -50,9 +74,8 @@ type RoundTripper struct {
|
|||
// If nil, reasonable default values will be used.
|
||||
QuicConfig *quic.Config
|
||||
|
||||
// Enable support for HTTP/3 datagrams.
|
||||
// If set to true, QuicConfig.EnableDatagram will be set.
|
||||
// See https://datatracker.ietf.org/doc/html/rfc9297.
|
||||
// Enable support for HTTP/3 datagrams (RFC 9297).
|
||||
// If a QuicConfig is set, datagram support also needs to be enabled on the QUIC layer by setting EnableDatagrams.
|
||||
EnableDatagrams bool
|
||||
|
||||
// Additional HTTP/3 settings.
|
||||
|
@ -89,16 +112,6 @@ type RoundTripper struct {
|
|||
transport *quic.Transport
|
||||
}
|
||||
|
||||
// RoundTripOpt are options for the Transport.RoundTripOpt method.
|
||||
type RoundTripOpt struct {
|
||||
// OnlyCachedConn controls whether the RoundTripper may create a new QUIC connection.
|
||||
// If set true and no cached connection is available, RoundTripOpt will return ErrNoCachedConn.
|
||||
OnlyCachedConn bool
|
||||
// DontCloseRequestStream controls whether the request stream is closed after sending the request.
|
||||
// If set, context cancellations have no effect after the response headers are received.
|
||||
DontCloseRequestStream bool
|
||||
}
|
||||
|
||||
var (
|
||||
_ http.RoundTripper = &RoundTripper{}
|
||||
_ io.Closer = &RoundTripper{}
|
||||
|
@ -204,6 +217,7 @@ func (r *RoundTripper) getClient(hostname string, onlyCached bool) (rtc *roundTr
|
|||
MaxHeaderBytes: r.MaxResponseHeaderBytes,
|
||||
StreamHijacker: r.StreamHijacker,
|
||||
UniStreamHijacker: r.UniStreamHijacker,
|
||||
AdditionalSettings: r.AdditionalSettings,
|
||||
},
|
||||
r.QuicConfig,
|
||||
dial,
|
||||
|
|
|
@ -72,6 +72,21 @@ var _ = Describe("RoundTripper", func() {
|
|||
Expect(err).To(MatchError(testErr))
|
||||
})
|
||||
|
||||
It("creates new clients with additional settings", func() {
|
||||
testErr := errors.New("test err")
|
||||
req, err := http.NewRequest("GET", "https://quic.clemente.io/foobar.html", nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
rt.AdditionalSettings = map[uint64]uint64{1337: 42}
|
||||
rt.newClient = func(_ string, _ *tls.Config, opts *roundTripperOpts, conf *quic.Config, _ dialFunc) (roundTripCloser, error) {
|
||||
cl := NewMockRoundTripCloser(mockCtrl)
|
||||
cl.EXPECT().RoundTripOpt(gomock.Any(), gomock.Any()).Return(nil, testErr)
|
||||
Expect(opts.AdditionalSettings).To(HaveKeyWithValue(uint64(1337), uint64(42)))
|
||||
return cl, nil
|
||||
}
|
||||
_, err = rt.RoundTrip(req)
|
||||
Expect(err).To(MatchError(testErr))
|
||||
})
|
||||
|
||||
It("uses the quic.Config, if provided", func() {
|
||||
config := &quic.Config{HandshakeIdleTimeout: time.Millisecond}
|
||||
var receivedConfig *quic.Config
|
||||
|
@ -85,6 +100,19 @@ var _ = Describe("RoundTripper", func() {
|
|||
Expect(receivedConfig.HandshakeIdleTimeout).To(Equal(config.HandshakeIdleTimeout))
|
||||
})
|
||||
|
||||
It("requires quic.Config.EnableDatagram if HTTP datagrams are enabled", func() {
|
||||
rt.QuicConfig = &quic.Config{EnableDatagrams: false}
|
||||
rt.Dial = func(_ context.Context, _ string, _ *tls.Config, config *quic.Config) (quic.EarlyConnection, error) {
|
||||
return nil, errors.New("handshake error")
|
||||
}
|
||||
rt.EnableDatagrams = true
|
||||
_, err := rt.RoundTrip(req)
|
||||
Expect(err).To(MatchError("HTTP Datagrams enabled, but QUIC Datagrams disabled"))
|
||||
rt.QuicConfig.EnableDatagrams = true
|
||||
_, err = rt.RoundTrip(req)
|
||||
Expect(err).To(MatchError("handshake error"))
|
||||
})
|
||||
|
||||
It("uses the custom dialer, if provided", func() {
|
||||
var dialed bool
|
||||
dialer := func(_ context.Context, _ string, tlsCfgP *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
tls "github.com/refraction-networking/utls"
|
||||
|
@ -32,6 +33,7 @@ var (
|
|||
quicListenAddr = func(addr string, tlsConf *tls.Config, config *quic.Config) (QUICEarlyListener, error) {
|
||||
return quic.ListenAddrEarly(addr, tlsConf, config)
|
||||
}
|
||||
errPanicked = errors.New("panicked")
|
||||
)
|
||||
|
||||
// NextProtoH3 is the ALPN protocol negotiated during the TLS handshake, for QUIC v1 and v2.
|
||||
|
@ -56,7 +58,7 @@ type QUICEarlyListener interface {
|
|||
|
||||
var _ QUICEarlyListener = &quic.EarlyListener{}
|
||||
|
||||
func versionToALPN(v protocol.VersionNumber) string {
|
||||
func versionToALPN(v protocol.Version) string {
|
||||
//nolint:exhaustive // These are all the versions we care about.
|
||||
switch v {
|
||||
case protocol.Version1, protocol.Version2:
|
||||
|
@ -78,7 +80,7 @@ func ConfigureTLSConfig(tlsConf *tls.Config) *tls.Config {
|
|||
// determine the ALPN from the QUIC version used
|
||||
proto := NextProtoH3
|
||||
val := ch.Context().Value(quic.QUICVersionContextKey)
|
||||
if v, ok := val.(quic.VersionNumber); ok {
|
||||
if v, ok := val.(quic.Version); ok {
|
||||
proto = versionToALPN(v)
|
||||
}
|
||||
config := tlsConf
|
||||
|
@ -117,6 +119,16 @@ func (k *contextKey) String() string { return "quic-go/http3 context value " + k
|
|||
// type *http3.Server.
|
||||
var ServerContextKey = &contextKey{"http3-server"}
|
||||
|
||||
// RemoteAddrContextKey is a context key. It can be used in
|
||||
// HTTP handlers with Context.Value to access the remote
|
||||
// address of the connection. The associated value will be of
|
||||
// type net.Addr.
|
||||
//
|
||||
// Use this value instead of [http.Request.RemoteAddr] if you
|
||||
// require access to the remote address of the connection rather
|
||||
// than its string representation.
|
||||
var RemoteAddrContextKey = &contextKey{"remote-addr"}
|
||||
|
||||
type requestError struct {
|
||||
err error
|
||||
streamErr ErrCode
|
||||
|
@ -202,6 +214,11 @@ type Server struct {
|
|||
// In that case, the stream type will not be set.
|
||||
UniStreamHijacker func(StreamType, quic.Connection, quic.ReceiveStream, error) (hijacked bool)
|
||||
|
||||
// ConnContext optionally specifies a function that modifies
|
||||
// the context used for a new connection c. The provided ctx
|
||||
// has a ServerContextKey value.
|
||||
ConnContext func(ctx context.Context, c quic.Connection) context.Context
|
||||
|
||||
mutex sync.RWMutex
|
||||
listeners map[*QUICEarlyListener]listenerInfo
|
||||
|
||||
|
@ -275,7 +292,7 @@ func (s *Server) ServeListener(ln QUICEarlyListener) error {
|
|||
}
|
||||
go func() {
|
||||
if err := s.handleConn(conn); err != nil {
|
||||
s.logger.Debugf(err.Error())
|
||||
s.logger.Debugf("handling connection failed: %s", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -409,10 +426,11 @@ func (s *Server) addListener(l *QUICEarlyListener) error {
|
|||
s.listeners = make(map[*QUICEarlyListener]listenerInfo)
|
||||
}
|
||||
|
||||
if port, err := extractPort((*l).Addr().String()); err == nil {
|
||||
laddr := (*l).Addr()
|
||||
if port, err := extractPort(laddr.String()); err == nil {
|
||||
s.listeners[l] = listenerInfo{port}
|
||||
} else {
|
||||
s.logger.Errorf("Unable to extract port from listener %+v, will not be announced using SetQuicHeaders: %s", err)
|
||||
s.logger.Errorf("Unable to extract port from listener %s, will not be announced using SetQuicHeaders: %s", laddr, err)
|
||||
s.listeners[l] = listenerInfo{}
|
||||
}
|
||||
s.generateAltSvcHeader()
|
||||
|
@ -436,7 +454,11 @@ func (s *Server) handleConn(conn quic.Connection) error {
|
|||
}
|
||||
b := make([]byte, 0, 64)
|
||||
b = quicvarint.Append(b, streamTypeControlStream) // stream type
|
||||
b = (&settingsFrame{Datagram: s.EnableDatagrams, Other: s.AdditionalSettings}).Append(b)
|
||||
b = (&settingsFrame{
|
||||
Datagram: s.EnableDatagrams,
|
||||
ExtendedConnect: true,
|
||||
Other: s.AdditionalSettings,
|
||||
}).Append(b)
|
||||
str.Write(b)
|
||||
|
||||
go s.handleUnidirectionalStreams(conn)
|
||||
|
@ -479,6 +501,8 @@ func (s *Server) handleConn(conn quic.Connection) error {
|
|||
}
|
||||
|
||||
func (s *Server) handleUnidirectionalStreams(conn quic.Connection) {
|
||||
var rcvdControlStream atomic.Bool
|
||||
|
||||
for {
|
||||
str, err := conn.AcceptUniStream(context.Background())
|
||||
if err != nil {
|
||||
|
@ -512,6 +536,11 @@ func (s *Server) handleUnidirectionalStreams(conn quic.Connection) {
|
|||
str.CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError))
|
||||
return
|
||||
}
|
||||
// Only a single control stream is allowed.
|
||||
if isFirstControlStr := rcvdControlStream.CompareAndSwap(false, true); !isFirstControlStr {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
|
||||
return
|
||||
}
|
||||
f, err := parseNextFrame(str, nil)
|
||||
if err != nil {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
|
||||
|
@ -617,8 +646,18 @@ func (s *Server) handleRequest(conn quic.Connection, str quic.Stream, decoder *q
|
|||
ctx := str.Context()
|
||||
ctx = context.WithValue(ctx, ServerContextKey, s)
|
||||
ctx = context.WithValue(ctx, http.LocalAddrContextKey, conn.LocalAddr())
|
||||
ctx = context.WithValue(ctx, RemoteAddrContextKey, conn.RemoteAddr())
|
||||
if s.ConnContext != nil {
|
||||
ctx = s.ConnContext(ctx, conn)
|
||||
if ctx == nil {
|
||||
panic("http3: ConnContext returned nil")
|
||||
}
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
r := newResponseWriter(str, conn, s.logger)
|
||||
if req.Method == http.MethodHead {
|
||||
r.isHead = true
|
||||
}
|
||||
handler := s.Handler
|
||||
if handler == nil {
|
||||
handler = http.DefaultServeMux
|
||||
|
@ -658,6 +697,11 @@ func (s *Server) handleRequest(conn quic.Connection, str quic.Stream, decoder *q
|
|||
}
|
||||
// If the EOF was read by the handler, CancelRead() is a no-op.
|
||||
str.CancelRead(quic.StreamErrorCode(ErrCodeNoError))
|
||||
|
||||
// abort the stream when there is a panic
|
||||
if panicked {
|
||||
return newStreamError(ErrCodeInternalError, errPanicked)
|
||||
}
|
||||
return requestError{}
|
||||
}
|
||||
|
||||
|
@ -722,7 +766,7 @@ func ListenAndServeQUIC(addr, certFile, keyFile string, handler http.Handler) er
|
|||
return server.ListenAndServeTLS(certFile, keyFile)
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the given network address for both, TLS and QUIC
|
||||
// ListenAndServe listens on the given network address for both TLS/TCP and QUIC
|
||||
// connections in parallel. It returns if one of the two returns an error.
|
||||
// http.DefaultServeMux is used when handler is nil.
|
||||
// The correct Alt-Svc headers for QUIC are set.
|
||||
|
@ -764,8 +808,8 @@ func ListenAndServe(addr, certFile, keyFile string, handler http.Handler) error
|
|||
Handler: handler,
|
||||
}
|
||||
|
||||
hErr := make(chan error)
|
||||
qErr := make(chan error)
|
||||
hErr := make(chan error, 1)
|
||||
qErr := make(chan error, 1)
|
||||
go func() {
|
||||
hErr <- http.ListenAndServeTLS(addr, certFile, keyFile, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
quicServer.SetQuicHeaders(w.Header())
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
quic "github.com/refraction-networking/uquic"
|
||||
mockquic "github.com/refraction-networking/uquic/internal/mocks/quic"
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/qerr"
|
||||
"github.com/refraction-networking/uquic/internal/testdata"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
"github.com/refraction-networking/uquic/quicvarint"
|
||||
|
@ -68,11 +69,15 @@ var _ = Describe("Server", func() {
|
|||
s *Server
|
||||
origQuicListenAddr = quicListenAddr
|
||||
)
|
||||
type testConnContextKey string
|
||||
|
||||
BeforeEach(func() {
|
||||
s = &Server{
|
||||
TLSConfig: testdata.GetTLSConfig(),
|
||||
logger: utils.DefaultLogger,
|
||||
ConnContext: func(ctx context.Context, c quic.Connection) context.Context {
|
||||
return context.WithValue(ctx, testConnContextKey("test"), c)
|
||||
},
|
||||
}
|
||||
origQuicListenAddr = quicListenAddr
|
||||
})
|
||||
|
@ -164,6 +169,7 @@ var _ = Describe("Server", func() {
|
|||
Expect(req.Host).To(Equal("www.example.com"))
|
||||
Expect(req.RemoteAddr).To(Equal("127.0.0.1:1337"))
|
||||
Expect(req.Context().Value(ServerContextKey)).To(Equal(s))
|
||||
Expect(req.Context().Value(testConnContextKey("test"))).ToNot(Equal(nil))
|
||||
})
|
||||
|
||||
It("returns 200 with an empty handler", func() {
|
||||
|
@ -222,6 +228,45 @@ var _ = Describe("Server", func() {
|
|||
Expect(hfs).To(HaveLen(3))
|
||||
})
|
||||
|
||||
It("response to HEAD request should not have body", func() {
|
||||
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("foobar"))
|
||||
})
|
||||
|
||||
headRequest, err := http.NewRequest("HEAD", "https://www.example.com", nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
responseBuf := &bytes.Buffer{}
|
||||
setRequest(encodeRequest(headRequest))
|
||||
str.EXPECT().Context().Return(reqContext)
|
||||
str.EXPECT().Write(gomock.Any()).DoAndReturn(responseBuf.Write).AnyTimes()
|
||||
str.EXPECT().CancelRead(gomock.Any())
|
||||
serr := s.handleRequest(conn, str, qpackDecoder, nil)
|
||||
Expect(serr.err).ToNot(HaveOccurred())
|
||||
hfs := decodeHeader(responseBuf)
|
||||
Expect(hfs).To(HaveKeyWithValue(":status", []string{"200"}))
|
||||
Expect(responseBuf.Bytes()).To(HaveLen(0))
|
||||
})
|
||||
|
||||
It("response to HEAD request should also do content sniffing", func() {
|
||||
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("<html></html>"))
|
||||
})
|
||||
|
||||
headRequest, err := http.NewRequest("HEAD", "https://www.example.com", nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
responseBuf := &bytes.Buffer{}
|
||||
setRequest(encodeRequest(headRequest))
|
||||
str.EXPECT().Context().Return(reqContext)
|
||||
str.EXPECT().Write(gomock.Any()).DoAndReturn(responseBuf.Write).AnyTimes()
|
||||
str.EXPECT().CancelRead(gomock.Any())
|
||||
serr := s.handleRequest(conn, str, qpackDecoder, nil)
|
||||
Expect(serr.err).ToNot(HaveOccurred())
|
||||
hfs := decodeHeader(responseBuf)
|
||||
Expect(hfs).To(HaveKeyWithValue(":status", []string{"200"}))
|
||||
Expect(hfs).To(HaveKeyWithValue("content-length", []string{"13"}))
|
||||
Expect(hfs).To(HaveKeyWithValue("content-type", []string{"text/html; charset=utf-8"}))
|
||||
})
|
||||
|
||||
It("handles a aborting handler", func() {
|
||||
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
panic(http.ErrAbortHandler)
|
||||
|
@ -234,7 +279,7 @@ var _ = Describe("Server", func() {
|
|||
str.EXPECT().CancelRead(gomock.Any())
|
||||
|
||||
serr := s.handleRequest(conn, str, qpackDecoder, nil)
|
||||
Expect(serr.err).ToNot(HaveOccurred())
|
||||
Expect(serr.err).To(MatchError(errPanicked))
|
||||
Expect(responseBuf.Bytes()).To(HaveLen(0))
|
||||
})
|
||||
|
||||
|
@ -250,7 +295,7 @@ var _ = Describe("Server", func() {
|
|||
str.EXPECT().CancelRead(gomock.Any())
|
||||
|
||||
serr := s.handleRequest(conn, str, qpackDecoder, nil)
|
||||
Expect(serr.err).ToNot(HaveOccurred())
|
||||
Expect(serr.err).To(MatchError(errPanicked))
|
||||
Expect(responseBuf.Bytes()).To(HaveLen(0))
|
||||
})
|
||||
|
||||
|
@ -454,7 +499,7 @@ var _ = Describe("Server", func() {
|
|||
|
||||
Context("control stream handling", func() {
|
||||
var conn *mockquic.MockEarlyConnection
|
||||
testDone := make(chan struct{})
|
||||
testDone := make(chan struct{}, 1)
|
||||
|
||||
BeforeEach(func() {
|
||||
conn = mockquic.NewMockEarlyConnection(mockCtrl)
|
||||
|
@ -485,6 +530,34 @@ var _ = Describe("Server", func() {
|
|||
time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to conn.CloseWithError
|
||||
})
|
||||
|
||||
It("rejects duplicate control streams", func() {
|
||||
b := quicvarint.Append(nil, streamTypeControlStream)
|
||||
b = (&settingsFrame{}).Append(b)
|
||||
r1 := bytes.NewReader(b)
|
||||
controlStr1 := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr1.EXPECT().Read(gomock.Any()).DoAndReturn(r1.Read).AnyTimes()
|
||||
r2 := bytes.NewReader(b)
|
||||
controlStr2 := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr2.EXPECT().Read(gomock.Any()).DoAndReturn(r2.Read).AnyTimes()
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(qerr.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream").Do(func(qerr.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr1, nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr2, nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
<-done
|
||||
return nil, errors.New("test done")
|
||||
})
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
|
||||
for _, t := range []uint64{streamTypeQPACKEncoderStream, streamTypeQPACKDecoderStream} {
|
||||
streamType := t
|
||||
name := "encoder"
|
||||
|
@ -514,9 +587,7 @@ var _ = Describe("Server", func() {
|
|||
str := mockquic.NewMockStream(mockCtrl)
|
||||
str.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
|
||||
done := make(chan struct{})
|
||||
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError)).Do(func(code quic.StreamErrorCode) {
|
||||
close(done)
|
||||
})
|
||||
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError)).Do(func(quic.StreamErrorCode) { close(done) })
|
||||
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return str, nil
|
||||
|
@ -543,10 +614,9 @@ var _ = Describe("Server", func() {
|
|||
return nil, errors.New("test done")
|
||||
})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeMissingSettings))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
@ -566,10 +636,9 @@ var _ = Describe("Server", func() {
|
|||
return nil, errors.New("test done")
|
||||
})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeFrameError))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
@ -589,10 +658,9 @@ var _ = Describe("Server", func() {
|
|||
return nil, errors.New("test done")
|
||||
})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeStreamCreationError))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
@ -614,11 +682,9 @@ var _ = Describe("Server", func() {
|
|||
})
|
||||
conn.EXPECT().ConnectionState().Return(quic.ConnectionState{SupportsDatagrams: false})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, reason string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeSettingsError))
|
||||
Expect(reason).To(Equal("missing QUIC Datagram support"))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeSettingsError), "missing QUIC Datagram support").Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
@ -664,7 +730,7 @@ var _ = Describe("Server", func() {
|
|||
str.EXPECT().Context().Return(reqContext)
|
||||
str.EXPECT().Write(gomock.Any()).DoAndReturn(responseBuf.Write).AnyTimes()
|
||||
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeNoError))
|
||||
str.EXPECT().Close().Do(func() { close(done) })
|
||||
str.EXPECT().Close().Do(func() error { close(done); return nil })
|
||||
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
@ -739,9 +805,9 @@ var _ = Describe("Server", func() {
|
|||
}).AnyTimes()
|
||||
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
Expect(code).To(Equal(quic.ApplicationErrorCode(ErrCodeFrameUnexpected)))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
@ -819,7 +885,7 @@ var _ = Describe("Server", func() {
|
|||
|
||||
Context("setting http headers", func() {
|
||||
BeforeEach(func() {
|
||||
s.QuicConfig = &quic.Config{Versions: []protocol.VersionNumber{protocol.Version1}}
|
||||
s.QuicConfig = &quic.Config{Versions: []protocol.Version{protocol.Version1}}
|
||||
})
|
||||
|
||||
var ln1 QUICEarlyListener
|
||||
|
@ -880,7 +946,7 @@ var _ = Describe("Server", func() {
|
|||
})
|
||||
|
||||
It("works if the quic.Config sets QUIC versions", func() {
|
||||
s.QuicConfig.Versions = []quic.VersionNumber{quic.Version1, quic.Version2}
|
||||
s.QuicConfig.Versions = []quic.Version{quic.Version1, quic.Version2}
|
||||
addListener(":443", &ln1)
|
||||
checkSetHeaders(Equal(http.Header{"Alt-Svc": {`h3=":443"; ma=2592000`}}))
|
||||
removeListener(&ln1)
|
||||
|
@ -919,7 +985,7 @@ var _ = Describe("Server", func() {
|
|||
})
|
||||
|
||||
It("doesn't duplicate Alt-Svc values", func() {
|
||||
s.QuicConfig.Versions = []quic.VersionNumber{quic.Version1, quic.Version1}
|
||||
s.QuicConfig.Versions = []quic.Version{quic.Version1, quic.Version1}
|
||||
addListener(":443", &ln1)
|
||||
checkSetHeaders(Equal(http.Header{"Alt-Svc": {`h3=":443"; ma=2592000`}}))
|
||||
removeListener(&ln1)
|
||||
|
@ -960,7 +1026,7 @@ var _ = Describe("Server", func() {
|
|||
Context("ConfigureTLSConfig", func() {
|
||||
It("advertises v1 by default", func() {
|
||||
conf := ConfigureTLSConfig(testdata.GetTLSConfig())
|
||||
ln, err := quic.ListenAddr("localhost:0", conf, &quic.Config{Versions: []quic.VersionNumber{quic.Version1}})
|
||||
ln, err := quic.ListenAddr("localhost:0", conf, &quic.Config{Versions: []quic.Version{quic.Version1}})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
c, err := quic.DialAddr(context.Background(), ln.Addr().String(), &tls.Config{InsecureSkipVerify: true, NextProtos: []string{NextProtoH3}}, nil)
|
||||
|
@ -988,7 +1054,7 @@ var _ = Describe("Server", func() {
|
|||
},
|
||||
}
|
||||
|
||||
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.VersionNumber{quic.Version1}})
|
||||
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.Version{quic.Version1}})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
c, err := quic.DialAddr(context.Background(), ln.Addr().String(), &tls.Config{InsecureSkipVerify: true, NextProtos: []string{NextProtoH3}}, nil)
|
||||
|
@ -1001,7 +1067,7 @@ var _ = Describe("Server", func() {
|
|||
tlsConf := testdata.GetTLSConfig()
|
||||
tlsConf.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { return nil, nil }
|
||||
|
||||
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.VersionNumber{quic.Version1}})
|
||||
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.Version{quic.Version1}})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
c, err := quic.DialAddr(context.Background(), ln.Addr().String(), &tls.Config{InsecureSkipVerify: true, NextProtos: []string{NextProtoH3}}, nil)
|
||||
|
@ -1019,7 +1085,7 @@ var _ = Describe("Server", func() {
|
|||
},
|
||||
}
|
||||
|
||||
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.VersionNumber{quic.Version1}})
|
||||
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.Version{quic.Version1}})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
c, err := quic.DialAddr(context.Background(), ln.Addr().String(), &tls.Config{InsecureSkipVerify: true, NextProtos: []string{NextProtoH3}}, nil)
|
||||
|
@ -1051,7 +1117,7 @@ var _ = Describe("Server", func() {
|
|||
}
|
||||
|
||||
stopAccept := make(chan struct{})
|
||||
ln.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
|
||||
ln.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
|
||||
<-stopAccept
|
||||
return nil, errors.New("closed")
|
||||
})
|
||||
|
@ -1064,7 +1130,7 @@ var _ = Describe("Server", func() {
|
|||
}()
|
||||
|
||||
Consistently(done).ShouldNot(BeClosed())
|
||||
ln.EXPECT().Close().Do(func() { close(stopAccept) })
|
||||
ln.EXPECT().Close().Do(func() error { close(stopAccept); return nil })
|
||||
Expect(s.Close()).To(Succeed())
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
|
@ -1086,13 +1152,13 @@ var _ = Describe("Server", func() {
|
|||
}
|
||||
|
||||
stopAccept1 := make(chan struct{})
|
||||
ln1.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
|
||||
ln1.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
|
||||
<-stopAccept1
|
||||
return nil, errors.New("closed")
|
||||
})
|
||||
ln1.EXPECT().Addr() // generate alt-svc headers
|
||||
stopAccept2 := make(chan struct{})
|
||||
ln2.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
|
||||
ln2.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
|
||||
<-stopAccept2
|
||||
return nil, errors.New("closed")
|
||||
})
|
||||
|
@ -1113,8 +1179,8 @@ var _ = Describe("Server", func() {
|
|||
|
||||
Consistently(done1).ShouldNot(BeClosed())
|
||||
Expect(done2).ToNot(BeClosed())
|
||||
ln1.EXPECT().Close().Do(func() { close(stopAccept1) })
|
||||
ln2.EXPECT().Close().Do(func() { close(stopAccept2) })
|
||||
ln1.EXPECT().Close().Do(func() error { close(stopAccept1); return nil })
|
||||
ln2.EXPECT().Close().Do(func() error { close(stopAccept2); return nil })
|
||||
Expect(s.Close()).To(Succeed())
|
||||
Eventually(done1).Should(BeClosed())
|
||||
Eventually(done2).Should(BeClosed())
|
||||
|
@ -1139,7 +1205,7 @@ var _ = Describe("Server", func() {
|
|||
s := &Server{}
|
||||
|
||||
stopAccept := make(chan struct{})
|
||||
ln.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
|
||||
ln.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
|
||||
<-stopAccept
|
||||
return nil, errors.New("closed")
|
||||
})
|
||||
|
@ -1153,7 +1219,7 @@ var _ = Describe("Server", func() {
|
|||
|
||||
Consistently(func() int32 { return atomic.LoadInt32(&called) }).Should(Equal(int32(0)))
|
||||
Consistently(done).ShouldNot(BeClosed())
|
||||
ln.EXPECT().Close().Do(func() { close(stopAccept) })
|
||||
ln.EXPECT().Close().Do(func() error { close(stopAccept); return nil })
|
||||
Expect(s.Close()).To(Succeed())
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
|
@ -1173,13 +1239,13 @@ var _ = Describe("Server", func() {
|
|||
s := &Server{}
|
||||
|
||||
stopAccept1 := make(chan struct{})
|
||||
ln1.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
|
||||
ln1.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
|
||||
<-stopAccept1
|
||||
return nil, errors.New("closed")
|
||||
})
|
||||
ln1.EXPECT().Addr() // generate alt-svc headers
|
||||
stopAccept2 := make(chan struct{})
|
||||
ln2.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
|
||||
ln2.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
|
||||
<-stopAccept2
|
||||
return nil, errors.New("closed")
|
||||
})
|
||||
|
@ -1201,8 +1267,8 @@ var _ = Describe("Server", func() {
|
|||
Consistently(func() int32 { return atomic.LoadInt32(&called) }).Should(Equal(int32(0)))
|
||||
Consistently(done1).ShouldNot(BeClosed())
|
||||
Expect(done2).ToNot(BeClosed())
|
||||
ln1.EXPECT().Close().Do(func() { close(stopAccept1) })
|
||||
ln2.EXPECT().Close().Do(func() { close(stopAccept2) })
|
||||
ln1.EXPECT().Close().Do(func() error { close(stopAccept1); return nil })
|
||||
ln2.EXPECT().Close().Do(func() error { close(stopAccept2); return nil })
|
||||
Expect(s.Close()).To(Succeed())
|
||||
Eventually(done1).Should(BeClosed())
|
||||
Eventually(done2).Should(BeClosed())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue