Просмотр исходного кода

Vendor Psiphon-Labs/quic-go/http3

Rod Hynes 6 лет назад
Родитель
Сommit
0074bdee43

+ 104 - 0
vendor/github.com/Psiphon-Labs/quic-go/http3/body.go

@@ -0,0 +1,104 @@
+package http3
+
+import (
+	"fmt"
+	"io"
+
+	"github.com/Psiphon-Labs/quic-go"
+)
+
+// The body of a http.Request or http.Response.
+type body struct {
+	str quic.Stream
+
+	isRequest bool
+
+	// only set for the http.Response
+	// The channel is closed when the user is done with this response:
+	// either when Read() errors, or when Close() is called.
+	reqDone       chan<- struct{}
+	reqDoneClosed bool
+
+	onFrameError func()
+
+	bytesRemainingInFrame uint64
+}
+
+var _ io.ReadCloser = &body{}
+
+func newRequestBody(str quic.Stream, onFrameError func()) *body {
+	return &body{
+		str:          str,
+		onFrameError: onFrameError,
+		isRequest:    true,
+	}
+}
+
+func newResponseBody(str quic.Stream, done chan<- struct{}, onFrameError func()) *body {
+	return &body{
+		str:          str,
+		onFrameError: onFrameError,
+		reqDone:      done,
+	}
+}
+
+func (r *body) Read(b []byte) (int, error) {
+	n, err := r.readImpl(b)
+	if err != nil && !r.isRequest {
+		r.requestDone()
+	}
+	return n, err
+}
+
+func (r *body) readImpl(b []byte) (int, error) {
+	if r.bytesRemainingInFrame == 0 {
+	parseLoop:
+		for {
+			frame, err := parseNextFrame(r.str)
+			if err != nil {
+				return 0, err
+			}
+			switch f := frame.(type) {
+			case *headersFrame:
+				// skip HEADERS frames
+				continue
+			case *dataFrame:
+				r.bytesRemainingInFrame = f.Length
+				break parseLoop
+			default:
+				r.onFrameError()
+				// parseNextFrame skips over unknown frame types
+				// Therefore, this condition is only entered when we parsed another known frame type.
+				return 0, fmt.Errorf("peer sent an unexpected frame: %T", f)
+			}
+		}
+	}
+
+	var n int
+	var err error
+	if r.bytesRemainingInFrame < uint64(len(b)) {
+		n, err = r.str.Read(b[:r.bytesRemainingInFrame])
+	} else {
+		n, err = r.str.Read(b)
+	}
+	r.bytesRemainingInFrame -= uint64(n)
+	return n, err
+}
+
+func (r *body) requestDone() {
+	if r.reqDoneClosed {
+		return
+	}
+	close(r.reqDone)
+	r.reqDoneClosed = true
+}
+
+func (r *body) Close() error {
+	// quic.Stream.Close() closes the write side, not the read side
+	if r.isRequest {
+		return r.str.Close()
+	}
+	r.requestDone()
+	r.str.CancelRead(quic.ErrorCode(errorRequestCanceled))
+	return nil
+}

+ 281 - 0
vendor/github.com/Psiphon-Labs/quic-go/http3/client.go

@@ -0,0 +1,281 @@
+package http3
+
+import (
+	"bytes"
+	"context"
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"strconv"
+	"sync"
+
+	"github.com/Psiphon-Labs/quic-go"
+	"github.com/Psiphon-Labs/quic-go/internal/utils"
+	"github.com/marten-seemann/qpack"
+)
+
+const defaultUserAgent = "quic-go HTTP/3"
+const defaultMaxResponseHeaderBytes = 10 * 1 << 20 // 10 MB
+
+var defaultQuicConfig = &quic.Config{
+	MaxIncomingStreams: -1, // don't allow the server to create bidirectional streams
+	KeepAlive:          true,
+}
+
+var dialAddr = quic.DialAddr
+
+type roundTripperOpts struct {
+	DisableCompression bool
+	MaxHeaderBytes     int64
+}
+
+// client is a HTTP3 client doing requests
+type client struct {
+	tlsConf *tls.Config
+	config  *quic.Config
+	opts    *roundTripperOpts
+
+	dialOnce     sync.Once
+	dialer       func(network, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.Session, error)
+	handshakeErr error
+
+	requestWriter *requestWriter
+
+	decoder *qpack.Decoder
+
+	hostname string
+
+	// [Psiphon]
+	setSession sync.Mutex
+
+	session quic.Session
+
+	logger utils.Logger
+}
+
+func newClient(
+	hostname string,
+	tlsConf *tls.Config,
+	opts *roundTripperOpts,
+	quicConfig *quic.Config,
+	dialer func(network, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.Session, error),
+) *client {
+	if tlsConf == nil {
+		tlsConf = &tls.Config{}
+	} else {
+		tlsConf = tlsConf.Clone()
+	}
+	// Replace existing ALPNs by H3
+	tlsConf.NextProtos = []string{nextProtoH3}
+	if quicConfig == nil {
+		quicConfig = defaultQuicConfig
+	}
+
+	// [Psiphon]
+	// Prevent race condition the results from concurrent RoundTrippers using defaultQuicConfig
+	if quicConfig.MaxIncomingStreams != -1 {
+		quicConfig.MaxIncomingStreams = -1 // don't allow any bidirectional streams
+	}
+
+	logger := utils.DefaultLogger.WithPrefix("h3 client")
+
+	return &client{
+		hostname:      authorityAddr("https", hostname),
+		tlsConf:       tlsConf,
+		requestWriter: newRequestWriter(logger),
+		decoder:       qpack.NewDecoder(func(hf qpack.HeaderField) {}),
+		config:        quicConfig,
+		opts:          opts,
+		dialer:        dialer,
+		logger:        logger,
+	}
+}
+
+func (c *client) dial() error {
+	var err error
+	var session quic.Session
+	if c.dialer != nil {
+		session, err = c.dialer("udp", c.hostname, c.tlsConf, c.config)
+	} else {
+		session, err = dialAddr(c.hostname, c.tlsConf, c.config)
+	}
+
+	// [Psiphon]
+	c.setSession.Lock()
+	c.session = session
+	c.setSession.Unlock()
+
+	if err != nil {
+		return err
+	}
+
+	go func() {
+		if err := c.setupSession(); err != nil {
+			c.logger.Debugf("Setting up session failed: %s", err)
+			c.session.CloseWithError(quic.ErrorCode(errorInternalError), "")
+		}
+	}()
+
+	return nil
+}
+
+func (c *client) setupSession() error {
+	// open the control stream
+	str, err := c.session.OpenUniStream()
+	if err != nil {
+		return err
+	}
+	buf := &bytes.Buffer{}
+	// write the type byte
+	buf.Write([]byte{0x0})
+	// send the SETTINGS frame
+	(&settingsFrame{}).Write(buf)
+	if _, err := str.Write(buf.Bytes()); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (c *client) Close() error {
+
+	// [Psiphon]
+	// Prevent panic when c.session is nil
+	c.setSession.Lock()
+	session := c.session
+	c.setSession.Unlock()
+	if session == nil {
+		return nil
+	}
+
+	return c.session.Close()
+}
+
+func (c *client) maxHeaderBytes() uint64 {
+	if c.opts.MaxHeaderBytes <= 0 {
+		return defaultMaxResponseHeaderBytes
+	}
+	return uint64(c.opts.MaxHeaderBytes)
+}
+
+// RoundTrip executes a request and returns a response
+func (c *client) RoundTrip(req *http.Request) (*http.Response, error) {
+	if req.URL.Scheme != "https" {
+		return nil, errors.New("http3: unsupported scheme")
+	}
+	if authorityAddr("https", hostnameFromRequest(req)) != c.hostname {
+		return nil, fmt.Errorf("http3 client BUG: RoundTrip called for the wrong client (expected %s, got %s)", c.hostname, req.Host)
+	}
+
+	c.dialOnce.Do(func() {
+		c.handshakeErr = c.dial()
+	})
+
+	if c.handshakeErr != nil {
+		return nil, c.handshakeErr
+	}
+
+	str, err := c.session.OpenStreamSync(context.Background())
+	if err != nil {
+		return nil, err
+	}
+
+	// Request Cancellation:
+	// This go routine keeps running even after RoundTrip() returns.
+	// It is shut down when the application is done processing the body.
+	reqDone := make(chan struct{})
+	go func() {
+		select {
+		case <-req.Context().Done():
+			str.CancelWrite(quic.ErrorCode(errorRequestCanceled))
+			str.CancelRead(quic.ErrorCode(errorRequestCanceled))
+		case <-reqDone:
+		}
+	}()
+
+	rsp, rerr := c.doRequest(req, str, reqDone)
+	if rerr.err != nil { // if any error occurred
+		close(reqDone)
+		if rerr.streamErr != 0 { // if it was a stream error
+			str.CancelWrite(quic.ErrorCode(rerr.streamErr))
+		}
+		if rerr.connErr != 0 { // if it was a connection error
+			var reason string
+			if rerr.err != nil {
+				reason = rerr.err.Error()
+			}
+			c.session.CloseWithError(quic.ErrorCode(rerr.connErr), reason)
+		}
+	}
+	return rsp, rerr.err
+}
+
+func (c *client) doRequest(
+	req *http.Request,
+	str quic.Stream,
+	reqDone chan struct{},
+) (*http.Response, requestError) {
+	var requestGzip bool
+	if !c.opts.DisableCompression && req.Method != "HEAD" && req.Header.Get("Accept-Encoding") == "" && req.Header.Get("Range") == "" {
+		requestGzip = true
+	}
+	if err := c.requestWriter.WriteRequest(str, req, requestGzip); err != nil {
+		return nil, newStreamError(errorInternalError, err)
+	}
+
+	frame, err := parseNextFrame(str)
+	if err != nil {
+		return nil, newStreamError(errorFrameError, err)
+	}
+	hf, ok := frame.(*headersFrame)
+	if !ok {
+		return nil, newConnError(errorFrameUnexpected, errors.New("expected first frame to be a HEADERS frame"))
+	}
+	if hf.Length > c.maxHeaderBytes() {
+		return nil, newStreamError(errorFrameError, fmt.Errorf("HEADERS frame too large: %d bytes (max: %d)", hf.Length, c.maxHeaderBytes()))
+	}
+	headerBlock := make([]byte, hf.Length)
+	if _, err := io.ReadFull(str, headerBlock); err != nil {
+		return nil, newStreamError(errorRequestIncomplete, err)
+	}
+	hfs, err := c.decoder.DecodeFull(headerBlock)
+	if err != nil {
+		// TODO: use the right error code
+		return nil, newConnError(errorGeneralProtocolError, err)
+	}
+
+	res := &http.Response{
+		Proto:      "HTTP/3",
+		ProtoMajor: 3,
+		Header:     http.Header{},
+	}
+	for _, hf := range hfs {
+		switch hf.Name {
+		case ":status":
+			status, err := strconv.Atoi(hf.Value)
+			if err != nil {
+				return nil, newStreamError(errorGeneralProtocolError, errors.New("malformed non-numeric status pseudo header"))
+			}
+			res.StatusCode = status
+			res.Status = hf.Value + " " + http.StatusText(status)
+		default:
+			res.Header.Add(hf.Name, hf.Value)
+		}
+	}
+	respBody := newResponseBody(str, reqDone, func() {
+		c.session.CloseWithError(quic.ErrorCode(errorFrameUnexpected), "")
+	})
+	if requestGzip && res.Header.Get("Content-Encoding") == "gzip" {
+		res.Header.Del("Content-Encoding")
+		res.Header.Del("Content-Length")
+		res.ContentLength = -1
+		res.Body = newGzipReader(respBody)
+		res.Uncompressed = true
+	} else {
+		res.Body = respBody
+	}
+
+	return res, requestError{}
+}

+ 70 - 0
vendor/github.com/Psiphon-Labs/quic-go/http3/error_codes.go

@@ -0,0 +1,70 @@
+package http3
+
+import (
+	"fmt"
+
+	quic "github.com/Psiphon-Labs/quic-go"
+)
+
+type errorCode quic.ErrorCode
+
+const (
+	errorNoError              errorCode = 0x100
+	errorGeneralProtocolError errorCode = 0x101
+	errorInternalError        errorCode = 0x102
+	errorStreamCreationError  errorCode = 0x103
+	errorClosedCriticalStream errorCode = 0x104
+	errorFrameUnexpected      errorCode = 0x105
+	errorFrameError           errorCode = 0x106
+	errorExcessiveLoad        errorCode = 0x107
+	errorIDError              errorCode = 0x108
+	errorSettingsError        errorCode = 0x109
+	errorMissingSettings      errorCode = 0x10a
+	errorRequestRejected      errorCode = 0x10b
+	errorRequestCanceled      errorCode = 0x10c
+	errorRequestIncomplete    errorCode = 0x10d
+	errorEarlyResponse        errorCode = 0x10e
+	errorConnectError         errorCode = 0x10f
+	errorVersionFallback      errorCode = 0x110
+)
+
+func (e errorCode) String() string {
+	switch e {
+	case errorNoError:
+		return "H3_NO_ERROR"
+	case errorGeneralProtocolError:
+		return "H3_GENERAL_PROTOCOL_ERROR"
+	case errorInternalError:
+		return "H3_INTERNAL_ERROR"
+	case errorStreamCreationError:
+		return "H3_STREAM_CREATION_ERROR"
+	case errorClosedCriticalStream:
+		return "H3_CLOSED_CRITICAL_STREAM"
+	case errorFrameUnexpected:
+		return "H3_FRAME_UNEXPECTED"
+	case errorFrameError:
+		return "H3_FRAME_ERROR"
+	case errorExcessiveLoad:
+		return "H3_EXCESSIVE_LOAD"
+	case errorIDError:
+		return "H3_ID_ERROR"
+	case errorSettingsError:
+		return "H3_SETTINGS_ERROR"
+	case errorMissingSettings:
+		return "H3_MISSING_SETTINGS"
+	case errorRequestRejected:
+		return "H3_REQUEST_REJECTED"
+	case errorRequestCanceled:
+		return "H3_REQUEST_CANCELLED"
+	case errorRequestIncomplete:
+		return "H3_INCOMPLETE_REQUEST"
+	case errorEarlyResponse:
+		return "H3_EARLY_RESPONSE"
+	case errorConnectError:
+		return "H3_CONNECT_ERROR"
+	case errorVersionFallback:
+		return "H3_VERSION_FALLBACK"
+	default:
+		return fmt.Sprintf("unknown error code: %#x", uint16(e))
+	}
+}

+ 133 - 0
vendor/github.com/Psiphon-Labs/quic-go/http3/frames.go

@@ -0,0 +1,133 @@
+package http3
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+
+	"github.com/Psiphon-Labs/quic-go/internal/protocol"
+	"github.com/Psiphon-Labs/quic-go/internal/utils"
+)
+
+type byteReader interface {
+	io.ByteReader
+	io.Reader
+}
+
+type byteReaderImpl struct{ io.Reader }
+
+func (br *byteReaderImpl) ReadByte() (byte, error) {
+	b := make([]byte, 1)
+	if _, err := br.Reader.Read(b); err != nil {
+		return 0, err
+	}
+	return b[0], nil
+}
+
+type frame interface{}
+
+func parseNextFrame(b io.Reader) (frame, error) {
+	br, ok := b.(byteReader)
+	if !ok {
+		br = &byteReaderImpl{b}
+	}
+	t, err := utils.ReadVarInt(br)
+	if err != nil {
+		return nil, err
+	}
+	l, err := utils.ReadVarInt(br)
+	if err != nil {
+		return nil, err
+	}
+
+	switch t {
+	case 0x0:
+		return &dataFrame{Length: l}, nil
+	case 0x1:
+		return &headersFrame{Length: l}, nil
+	case 0x4:
+		return parseSettingsFrame(br, l)
+	case 0x3: // CANCEL_PUSH
+		fallthrough
+	case 0x5: // PUSH_PROMISE
+		fallthrough
+	case 0x7: // GOAWAY
+		fallthrough
+	case 0xd: // MAX_PUSH_ID
+		fallthrough
+	case 0xe: // DUPLICATE_PUSH
+		fallthrough
+	default:
+		// skip over unknown frames
+		if _, err := io.CopyN(ioutil.Discard, br, int64(l)); err != nil {
+			return nil, err
+		}
+		return parseNextFrame(b)
+	}
+}
+
+type dataFrame struct {
+	Length uint64
+}
+
+func (f *dataFrame) Write(b *bytes.Buffer) {
+	utils.WriteVarInt(b, 0x0)
+	utils.WriteVarInt(b, f.Length)
+}
+
+type headersFrame struct {
+	Length uint64
+}
+
+func (f *headersFrame) Write(b *bytes.Buffer) {
+	utils.WriteVarInt(b, 0x1)
+	utils.WriteVarInt(b, f.Length)
+}
+
+type settingsFrame struct {
+	settings map[uint64]uint64
+}
+
+func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
+	if l > 8*(1<<10) {
+		return nil, fmt.Errorf("unexpected size for SETTINGS frame: %d", l)
+	}
+	buf := make([]byte, l)
+	if _, err := io.ReadFull(r, buf); err != nil {
+		if err == io.ErrUnexpectedEOF {
+			return nil, io.EOF
+		}
+		return nil, err
+	}
+	frame := &settingsFrame{settings: make(map[uint64]uint64)}
+	b := bytes.NewReader(buf)
+	for b.Len() > 0 {
+		id, err := utils.ReadVarInt(b)
+		if err != nil { // should not happen. We allocated the whole frame already.
+			return nil, err
+		}
+		val, err := utils.ReadVarInt(b)
+		if err != nil { // should not happen. We allocated the whole frame already.
+			return nil, err
+		}
+		if _, ok := frame.settings[id]; ok {
+			return nil, fmt.Errorf("duplicate setting: %d", id)
+		}
+		frame.settings[id] = val
+	}
+	return frame, nil
+}
+
+func (f *settingsFrame) Write(b *bytes.Buffer) {
+	utils.WriteVarInt(b, 0x4)
+	var l protocol.ByteCount
+	for id, val := range f.settings {
+		l += utils.VarIntLen(id) + utils.VarIntLen(val)
+	}
+	utils.WriteVarInt(b, uint64(l))
+	for id, val := range f.settings {
+		utils.WriteVarInt(b, id)
+		utils.WriteVarInt(b, val)
+	}
+}

+ 39 - 0
vendor/github.com/Psiphon-Labs/quic-go/http3/gzip_reader.go

@@ -0,0 +1,39 @@
+package http3
+
+// copied from net/transport.go
+
+// gzipReader wraps a response body so it can lazily
+// call gzip.NewReader on the first call to Read
+import (
+	"compress/gzip"
+	"io"
+)
+
+// call gzip.NewReader on the first call to Read
+type gzipReader struct {
+	body io.ReadCloser // underlying Response.Body
+	zr   *gzip.Reader  // lazily-initialized gzip reader
+	zerr error         // sticky error
+}
+
+func newGzipReader(body io.ReadCloser) io.ReadCloser {
+	return &gzipReader{body: body}
+}
+
+func (gz *gzipReader) Read(p []byte) (n int, err error) {
+	if gz.zerr != nil {
+		return 0, gz.zerr
+	}
+	if gz.zr == nil {
+		gz.zr, err = gzip.NewReader(gz.body)
+		if err != nil {
+			gz.zerr = err
+			return 0, err
+		}
+	}
+	return gz.zr.Read(p)
+}
+
+func (gz *gzipReader) Close() error {
+	return gz.body.Close()
+}

+ 77 - 0
vendor/github.com/Psiphon-Labs/quic-go/http3/request.go

@@ -0,0 +1,77 @@
+package http3
+
+import (
+	"crypto/tls"
+	"errors"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+
+	"github.com/marten-seemann/qpack"
+)
+
+func requestFromHeaders(headers []qpack.HeaderField) (*http.Request, error) {
+	var path, authority, method, contentLengthStr string
+	httpHeaders := http.Header{}
+
+	for _, h := range headers {
+		switch h.Name {
+		case ":path":
+			path = h.Value
+		case ":method":
+			method = h.Value
+		case ":authority":
+			authority = h.Value
+		case "content-length":
+			contentLengthStr = h.Value
+		default:
+			if !h.IsPseudo() {
+				httpHeaders.Add(h.Name, h.Value)
+			}
+		}
+	}
+
+	// concatenate cookie headers, see https://tools.ietf.org/html/rfc6265#section-5.4
+	if len(httpHeaders["Cookie"]) > 0 {
+		httpHeaders.Set("Cookie", strings.Join(httpHeaders["Cookie"], "; "))
+	}
+
+	if len(path) == 0 || len(authority) == 0 || len(method) == 0 {
+		return nil, errors.New(":path, :authority and :method must not be empty")
+	}
+
+	u, err := url.ParseRequestURI(path)
+	if err != nil {
+		return nil, err
+	}
+
+	var contentLength int64
+	if len(contentLengthStr) > 0 {
+		contentLength, err = strconv.ParseInt(contentLengthStr, 10, 64)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return &http.Request{
+		Method:        method,
+		URL:           u,
+		Proto:         "HTTP/3",
+		ProtoMajor:    3,
+		ProtoMinor:    0,
+		Header:        httpHeaders,
+		Body:          nil,
+		ContentLength: contentLength,
+		Host:          authority,
+		RequestURI:    path,
+		TLS:           &tls.ConnectionState{},
+	}, nil
+}
+
+func hostnameFromRequest(req *http.Request) string {
+	if req.URL != nil {
+		return req.URL.Host
+	}
+	return ""
+}

+ 312 - 0
vendor/github.com/Psiphon-Labs/quic-go/http3/request_writer.go

@@ -0,0 +1,312 @@
+package http3
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"net"
+	"net/http"
+	"strconv"
+	"strings"
+	"sync"
+
+	"github.com/Psiphon-Labs/quic-go"
+	"github.com/Psiphon-Labs/quic-go/internal/utils"
+	"github.com/marten-seemann/qpack"
+	"golang.org/x/net/http/httpguts"
+	"golang.org/x/net/http2/hpack"
+	"golang.org/x/net/idna"
+)
+
+type requestWriter struct {
+	mutex     sync.Mutex
+	encoder   *qpack.Encoder
+	headerBuf *bytes.Buffer
+
+	logger utils.Logger
+}
+
+func newRequestWriter(logger utils.Logger) *requestWriter {
+	headerBuf := &bytes.Buffer{}
+	encoder := qpack.NewEncoder(headerBuf)
+	return &requestWriter{
+		encoder:   encoder,
+		headerBuf: headerBuf,
+		logger:    logger,
+	}
+}
+
+func (w *requestWriter) WriteRequest(str quic.Stream, req *http.Request, gzip bool) error {
+	headers, err := w.getHeaders(req, gzip)
+	if err != nil {
+		return err
+	}
+	if _, err := str.Write(headers); err != nil {
+		return err
+	}
+	// TODO: add support for trailers
+	if req.Body == nil {
+		str.Close()
+		return nil
+	}
+
+	// send the request body asynchronously
+	go func() {
+		if err := w.sendRequestBody(req.Body, str); err != nil {
+			w.logger.Errorf("Error writing request: %s", err)
+			return
+		}
+		str.Close()
+	}()
+
+	return nil
+}
+
+func (w *requestWriter) getHeaders(req *http.Request, gzip bool) ([]byte, error) {
+	w.mutex.Lock()
+	defer w.mutex.Unlock()
+	defer w.encoder.Close()
+
+	if err := w.encodeHeaders(req, gzip, "", actualContentLength(req)); err != nil {
+		return nil, err
+	}
+
+	buf := &bytes.Buffer{}
+	hf := headersFrame{Length: uint64(w.headerBuf.Len())}
+	hf.Write(buf)
+	if _, err := io.Copy(buf, w.headerBuf); err != nil {
+		return nil, err
+	}
+	w.headerBuf.Reset()
+	return buf.Bytes(), nil
+}
+
+func (w *requestWriter) sendRequestBody(req io.ReadCloser, str quic.Stream) error {
+	b := make([]byte, 8*1024)
+	for {
+		n, err := req.Read(b)
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			str.CancelWrite(quic.ErrorCode(errorRequestCanceled))
+			return err
+		}
+		buf := &bytes.Buffer{}
+		(&dataFrame{Length: uint64(n)}).Write(buf)
+		if _, err := str.Write(buf.Bytes()); err != nil {
+			return err
+		}
+		if _, err := str.Write(b[:n]); err != nil {
+			return err
+		}
+	}
+	req.Close()
+	return nil
+}
+
+// copied from net/transport.go
+
+func (w *requestWriter) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) error {
+	host := req.Host
+	if host == "" {
+		host = req.URL.Host
+	}
+	host, err := httpguts.PunycodeHostPort(host)
+	if err != nil {
+		return err
+	}
+
+	var path string
+	if req.Method != "CONNECT" {
+		path = req.URL.RequestURI()
+		if !validPseudoPath(path) {
+			orig := path
+			path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host)
+			if !validPseudoPath(path) {
+				if req.URL.Opaque != "" {
+					return fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque)
+				} else {
+					return fmt.Errorf("invalid request :path %q", orig)
+				}
+			}
+		}
+	}
+
+	// Check for any invalid headers and return an error before we
+	// potentially pollute our hpack state. (We want to be able to
+	// continue to reuse the hpack encoder for future requests)
+	for k, vv := range req.Header {
+		if !httpguts.ValidHeaderFieldName(k) {
+			return fmt.Errorf("invalid HTTP header name %q", k)
+		}
+		for _, v := range vv {
+			if !httpguts.ValidHeaderFieldValue(v) {
+				return fmt.Errorf("invalid HTTP header value %q for header %q", v, k)
+			}
+		}
+	}
+
+	enumerateHeaders := func(f func(name, value string)) {
+		// 8.1.2.3 Request Pseudo-Header Fields
+		// The :path pseudo-header field includes the path and query parts of the
+		// target URI (the path-absolute production and optionally a '?' character
+		// followed by the query production (see Sections 3.3 and 3.4 of
+		// [RFC3986]).
+		f(":authority", host)
+		f(":method", req.Method)
+		if req.Method != "CONNECT" {
+			f(":path", path)
+			f(":scheme", req.URL.Scheme)
+		}
+		if trailers != "" {
+			f("trailer", trailers)
+		}
+
+		var didUA bool
+		for k, vv := range req.Header {
+			if strings.EqualFold(k, "host") || strings.EqualFold(k, "content-length") {
+				// Host is :authority, already sent.
+				// Content-Length is automatic, set below.
+				continue
+			} else if strings.EqualFold(k, "connection") || strings.EqualFold(k, "proxy-connection") ||
+				strings.EqualFold(k, "transfer-encoding") || strings.EqualFold(k, "upgrade") ||
+				strings.EqualFold(k, "keep-alive") {
+				// Per 8.1.2.2 Connection-Specific Header
+				// Fields, don't send connection-specific
+				// fields. We have already checked if any
+				// are error-worthy so just ignore the rest.
+				continue
+			} else if strings.EqualFold(k, "user-agent") {
+				// Match Go's http1 behavior: at most one
+				// User-Agent. If set to nil or empty string,
+				// then omit it. Otherwise if not mentioned,
+				// include the default (below).
+				didUA = true
+				if len(vv) < 1 {
+					continue
+				}
+				vv = vv[:1]
+				if vv[0] == "" {
+					continue
+				}
+
+			}
+
+			for _, v := range vv {
+				f(k, v)
+			}
+		}
+		if shouldSendReqContentLength(req.Method, contentLength) {
+			f("content-length", strconv.FormatInt(contentLength, 10))
+		}
+		if addGzipHeader {
+			f("accept-encoding", "gzip")
+		}
+		if !didUA {
+			f("user-agent", defaultUserAgent)
+		}
+	}
+
+	// Do a first pass over the headers counting bytes to ensure
+	// we don't exceed cc.peerMaxHeaderListSize. This is done as a
+	// separate pass before encoding the headers to prevent
+	// modifying the hpack state.
+	hlSize := uint64(0)
+	enumerateHeaders(func(name, value string) {
+		hf := hpack.HeaderField{Name: name, Value: value}
+		hlSize += uint64(hf.Size())
+	})
+
+	// TODO: check maximum header list size
+	// if hlSize > cc.peerMaxHeaderListSize {
+	// 	return errRequestHeaderListSize
+	// }
+
+	// trace := httptrace.ContextClientTrace(req.Context())
+	// traceHeaders := traceHasWroteHeaderField(trace)
+
+	// Header list size is ok. Write the headers.
+	enumerateHeaders(func(name, value string) {
+		name = strings.ToLower(name)
+		w.encoder.WriteField(qpack.HeaderField{Name: name, Value: value})
+		// if traceHeaders {
+		// 	traceWroteHeaderField(trace, name, value)
+		// }
+	})
+
+	return nil
+}
+
+// authorityAddr returns a given authority (a host/IP, or host:port / ip:port)
+// and returns a host:port. The port 443 is added if needed.
+func authorityAddr(scheme string, authority string) (addr string) {
+	host, port, err := net.SplitHostPort(authority)
+	if err != nil { // authority didn't have a port
+		port = "443"
+		if scheme == "http" {
+			port = "80"
+		}
+		host = authority
+	}
+	if a, err := idna.ToASCII(host); err == nil {
+		host = a
+	}
+	// IPv6 address literal, without a port:
+	if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
+		return host + ":" + port
+	}
+	return net.JoinHostPort(host, port)
+}
+
+// validPseudoPath reports whether v is a valid :path pseudo-header
+// value. It must be either:
+//
+//     *) a non-empty string starting with '/'
+//     *) the string '*', for OPTIONS requests.
+//
+// For now this is only used a quick check for deciding when to clean
+// up Opaque URLs before sending requests from the Transport.
+// See golang.org/issue/16847
+//
+// We used to enforce that the path also didn't start with "//", but
+// Google's GFE accepts such paths and Chrome sends them, so ignore
+// that part of the spec. See golang.org/issue/19103.
+func validPseudoPath(v string) bool {
+	return (len(v) > 0 && v[0] == '/') || v == "*"
+}
+
+// actualContentLength returns a sanitized version of
+// req.ContentLength, where 0 actually means zero (not unknown) and -1
+// means unknown.
+func actualContentLength(req *http.Request) int64 {
+	if req.Body == nil {
+		return 0
+	}
+	if req.ContentLength != 0 {
+		return req.ContentLength
+	}
+	return -1
+}
+
+// shouldSendReqContentLength reports whether the http2.Transport should send
+// a "content-length" request header. This logic is basically a copy of the net/http
+// transferWriter.shouldSendContentLength.
+// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown).
+// -1 means unknown.
+func shouldSendReqContentLength(method string, contentLength int64) bool {
+	if contentLength > 0 {
+		return true
+	}
+	if contentLength < 0 {
+		return false
+	}
+	// For zero bodies, whether we send a content-length depends on the method.
+	// It also kinda doesn't matter for http2 either way, with END_STREAM.
+	switch method {
+	case "POST", "PUT", "PATCH":
+		return true
+	default:
+		return false
+	}
+}

+ 100 - 0
vendor/github.com/Psiphon-Labs/quic-go/http3/response_writer.go

@@ -0,0 +1,100 @@
+package http3
+
+import (
+	"bytes"
+	"io"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/Psiphon-Labs/quic-go/internal/utils"
+	"github.com/marten-seemann/qpack"
+)
+
+type responseWriter struct {
+	stream io.Writer
+
+	header        http.Header
+	status        int // status code passed to WriteHeader
+	headerWritten bool
+
+	logger utils.Logger
+}
+
+var _ http.ResponseWriter = &responseWriter{}
+
+func newResponseWriter(stream io.Writer, logger utils.Logger) *responseWriter {
+	return &responseWriter{
+		header: http.Header{},
+		stream: stream,
+		logger: logger,
+	}
+}
+
+func (w *responseWriter) Header() http.Header {
+	return w.header
+}
+
+func (w *responseWriter) WriteHeader(status int) {
+	if w.headerWritten {
+		return
+	}
+	w.headerWritten = true
+	w.status = status
+
+	var headers bytes.Buffer
+	enc := qpack.NewEncoder(&headers)
+	enc.WriteField(qpack.HeaderField{Name: ":status", Value: strconv.Itoa(status)})
+
+	for k, v := range w.header {
+		for index := range v {
+			enc.WriteField(qpack.HeaderField{Name: strings.ToLower(k), Value: v[index]})
+		}
+	}
+
+	buf := &bytes.Buffer{}
+	(&headersFrame{Length: uint64(headers.Len())}).Write(buf)
+	w.logger.Infof("Responding with %d", status)
+	if _, err := w.stream.Write(buf.Bytes()); err != nil {
+		w.logger.Errorf("could not write headers frame: %s", err.Error())
+	}
+	if _, err := w.stream.Write(headers.Bytes()); err != nil {
+		w.logger.Errorf("could not write header frame payload: %s", err.Error())
+	}
+}
+
+func (w *responseWriter) Write(p []byte) (int, error) {
+	if !w.headerWritten {
+		w.WriteHeader(200)
+	}
+	if !bodyAllowedForStatus(w.status) {
+		return 0, http.ErrBodyNotAllowed
+	}
+	df := &dataFrame{Length: uint64(len(p))}
+	buf := &bytes.Buffer{}
+	df.Write(buf)
+	if _, err := w.stream.Write(buf.Bytes()); err != nil {
+		return 0, err
+	}
+	return w.stream.Write(p)
+}
+
+func (w *responseWriter) Flush() {}
+
+// test that we implement http.Flusher
+var _ http.Flusher = &responseWriter{}
+
+// copied from http2/http2.go
+// bodyAllowedForStatus reports whether a given response status code
+// permits a body. See RFC 2616, section 4.4.
+func bodyAllowedForStatus(status int) bool {
+	switch {
+	case status >= 100 && status <= 199:
+		return false
+	case status == 204:
+		return false
+	case status == 304:
+		return false
+	}
+	return true
+}

+ 187 - 0
vendor/github.com/Psiphon-Labs/quic-go/http3/roundtrip.go

@@ -0,0 +1,187 @@
+package http3
+
+import (
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+	"sync"
+
+	quic "github.com/Psiphon-Labs/quic-go"
+
+	"golang.org/x/net/http/httpguts"
+)
+
+type roundTripCloser interface {
+	http.RoundTripper
+	io.Closer
+}
+
+// RoundTripper implements the http.RoundTripper interface
+type RoundTripper struct {
+	mutex sync.Mutex
+
+	// DisableCompression, if true, prevents the Transport from
+	// requesting compression with an "Accept-Encoding: gzip"
+	// request header when the Request contains no existing
+	// Accept-Encoding value. If the Transport requests gzip on
+	// its own and gets a gzipped response, it's transparently
+	// decoded in the Response.Body. However, if the user
+	// explicitly requested gzip it is not automatically
+	// uncompressed.
+	DisableCompression bool
+
+	// TLSClientConfig specifies the TLS configuration to use with
+	// tls.Client. If nil, the default configuration is used.
+	TLSClientConfig *tls.Config
+
+	// QuicConfig is the quic.Config used for dialing new connections.
+	// If nil, reasonable default values will be used.
+	QuicConfig *quic.Config
+
+	// Dial specifies an optional dial function for creating QUIC
+	// connections for requests.
+	// If Dial is nil, quic.DialAddr will be used.
+	Dial func(network, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.Session, error)
+
+	// MaxResponseHeaderBytes specifies a limit on how many response bytes are
+	// allowed in the server's response header.
+	// Zero means to use a default limit.
+	MaxResponseHeaderBytes int64
+
+	clients map[string]roundTripCloser
+}
+
+// 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, RoundTrip
+	// will return ErrNoCachedConn.
+	OnlyCachedConn bool
+}
+
+var _ roundTripCloser = &RoundTripper{}
+
+// ErrNoCachedConn is returned when RoundTripper.OnlyCachedConn is set
+var ErrNoCachedConn = errors.New("http3: no cached connection was available")
+
+// RoundTripOpt is like RoundTrip, but takes options.
+func (r *RoundTripper) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
+	if req.URL == nil {
+		closeRequestBody(req)
+		return nil, errors.New("http3: nil Request.URL")
+	}
+	if req.URL.Host == "" {
+		closeRequestBody(req)
+		return nil, errors.New("http3: no Host in request URL")
+	}
+	if req.Header == nil {
+		closeRequestBody(req)
+		return nil, errors.New("http3: nil Request.Header")
+	}
+
+	if req.URL.Scheme == "https" {
+		for k, vv := range req.Header {
+			if !httpguts.ValidHeaderFieldName(k) {
+				return nil, fmt.Errorf("http3: invalid http header field name %q", k)
+			}
+			for _, v := range vv {
+				if !httpguts.ValidHeaderFieldValue(v) {
+					return nil, fmt.Errorf("http3: invalid http header field value %q for key %v", v, k)
+				}
+			}
+		}
+	} else {
+		closeRequestBody(req)
+		return nil, fmt.Errorf("http3: unsupported protocol scheme: %s", req.URL.Scheme)
+	}
+
+	if req.Method != "" && !validMethod(req.Method) {
+		closeRequestBody(req)
+		return nil, fmt.Errorf("http3: invalid method %q", req.Method)
+	}
+
+	hostname := authorityAddr("https", hostnameFromRequest(req))
+	cl, err := r.getClient(hostname, opt.OnlyCachedConn)
+	if err != nil {
+		return nil, err
+	}
+	return cl.RoundTrip(req)
+}
+
+// RoundTrip does a round trip.
+func (r *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
+	return r.RoundTripOpt(req, RoundTripOpt{})
+}
+
+func (r *RoundTripper) getClient(hostname string, onlyCached bool) (http.RoundTripper, error) {
+	r.mutex.Lock()
+	defer r.mutex.Unlock()
+
+	if r.clients == nil {
+		r.clients = make(map[string]roundTripCloser)
+	}
+
+	client, ok := r.clients[hostname]
+	if !ok {
+		if onlyCached {
+			return nil, ErrNoCachedConn
+		}
+		client = newClient(
+			hostname,
+			r.TLSClientConfig,
+			&roundTripperOpts{
+				DisableCompression: r.DisableCompression,
+				MaxHeaderBytes:     r.MaxResponseHeaderBytes,
+			},
+			r.QuicConfig,
+			r.Dial,
+		)
+		r.clients[hostname] = client
+	}
+	return client, nil
+}
+
+// Close closes the QUIC connections that this RoundTripper has used
+func (r *RoundTripper) Close() error {
+	r.mutex.Lock()
+	defer r.mutex.Unlock()
+	for _, client := range r.clients {
+		if err := client.Close(); err != nil {
+			return err
+		}
+	}
+	r.clients = nil
+	return nil
+}
+
+func closeRequestBody(req *http.Request) {
+	if req.Body != nil {
+		req.Body.Close()
+	}
+}
+
+func validMethod(method string) bool {
+	/*
+				     Method         = "OPTIONS"                ; Section 9.2
+		   		                    | "GET"                    ; Section 9.3
+		   		                    | "HEAD"                   ; Section 9.4
+		   		                    | "POST"                   ; Section 9.5
+		   		                    | "PUT"                    ; Section 9.6
+		   		                    | "DELETE"                 ; Section 9.7
+		   		                    | "TRACE"                  ; Section 9.8
+		   		                    | "CONNECT"                ; Section 9.9
+		   		                    | extension-method
+		   		   extension-method = token
+		   		     token          = 1*<any CHAR except CTLs or separators>
+	*/
+	return len(method) > 0 && strings.IndexFunc(method, isNotToken) == -1
+}
+
+// copied from net/http/http.go
+func isNotToken(r rune) bool {
+	return !httpguts.IsTokenRune(r)
+}

+ 427 - 0
vendor/github.com/Psiphon-Labs/quic-go/http3/server.go

@@ -0,0 +1,427 @@
+package http3
+
+import (
+	"bytes"
+	"context"
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"io"
+	"net"
+	"net/http"
+	"runtime"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"github.com/Psiphon-Labs/quic-go"
+	"github.com/Psiphon-Labs/quic-go/internal/utils"
+	"github.com/marten-seemann/qpack"
+	// [Psiphon]
+	// Remove testing dependency.
+	//"github.com/onsi/ginkgo"
+)
+
+// allows mocking of quic.Listen and quic.ListenAddr
+var (
+	quicListen     = quic.Listen
+	quicListenAddr = quic.ListenAddr
+)
+
+const nextProtoH3 = "h3-24"
+
+type requestError struct {
+	err       error
+	streamErr errorCode
+	connErr   errorCode
+}
+
+func newStreamError(code errorCode, err error) requestError {
+	return requestError{err: err, streamErr: code}
+}
+
+func newConnError(code errorCode, err error) requestError {
+	return requestError{err: err, connErr: code}
+}
+
+// Server is a HTTP2 server listening for QUIC connections.
+type Server struct {
+	*http.Server
+
+	// By providing a quic.Config, it is possible to set parameters of the QUIC connection.
+	// If nil, it uses reasonable default values.
+	QuicConfig *quic.Config
+
+	port uint32 // used atomically
+
+	mutex     sync.Mutex
+	listeners map[*quic.Listener]struct{}
+	closed    utils.AtomicBool
+
+	logger utils.Logger
+}
+
+// ListenAndServe listens on the UDP address s.Addr and calls s.Handler to handle HTTP/3 requests on incoming connections.
+func (s *Server) ListenAndServe() error {
+	if s.Server == nil {
+		return errors.New("use of http3.Server without http.Server")
+	}
+	return s.serveImpl(s.TLSConfig, nil)
+}
+
+// ListenAndServeTLS listens on the UDP address s.Addr and calls s.Handler to handle HTTP/3 requests on incoming connections.
+func (s *Server) ListenAndServeTLS(certFile, keyFile string) error {
+	var err error
+	certs := make([]tls.Certificate, 1)
+	certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
+	if err != nil {
+		return err
+	}
+	// We currently only use the cert-related stuff from tls.Config,
+	// so we don't need to make a full copy.
+	config := &tls.Config{
+		Certificates: certs,
+	}
+	return s.serveImpl(config, nil)
+}
+
+// Serve an existing UDP connection.
+// It is possible to reuse the same connection for outgoing connections.
+// Closing the server does not close the packet conn.
+func (s *Server) Serve(conn net.PacketConn) error {
+	return s.serveImpl(s.TLSConfig, conn)
+}
+
+func (s *Server) serveImpl(tlsConf *tls.Config, conn net.PacketConn) error {
+	if s.closed.Get() {
+		return http.ErrServerClosed
+	}
+	if s.Server == nil {
+		return errors.New("use of http3.Server without http.Server")
+	}
+	s.logger = utils.DefaultLogger.WithPrefix("server")
+
+	if tlsConf == nil {
+		tlsConf = &tls.Config{}
+	} else {
+		tlsConf = tlsConf.Clone()
+	}
+	// Replace existing ALPNs by H3
+	tlsConf.NextProtos = []string{nextProtoH3}
+	if tlsConf.GetConfigForClient != nil {
+		getConfigForClient := tlsConf.GetConfigForClient
+		tlsConf.GetConfigForClient = func(ch *tls.ClientHelloInfo) (*tls.Config, error) {
+			conf, err := getConfigForClient(ch)
+			if err != nil || conf == nil {
+				return conf, err
+			}
+			conf = conf.Clone()
+			conf.NextProtos = []string{nextProtoH3}
+			return conf, nil
+		}
+	}
+
+	var ln quic.Listener
+	var err error
+	if conn == nil {
+		ln, err = quicListenAddr(s.Addr, tlsConf, s.QuicConfig)
+	} else {
+		ln, err = quicListen(conn, tlsConf, s.QuicConfig)
+	}
+	if err != nil {
+		return err
+	}
+	s.addListener(&ln)
+	defer s.removeListener(&ln)
+
+	for {
+		sess, err := ln.Accept(context.Background())
+		if err != nil {
+			return err
+		}
+		go s.handleConn(sess)
+	}
+}
+
+// We store a pointer to interface in the map set. This is safe because we only
+// call trackListener via Serve and can track+defer untrack the same pointer to
+// local variable there. We never need to compare a Listener from another caller.
+func (s *Server) addListener(l *quic.Listener) {
+	s.mutex.Lock()
+	if s.listeners == nil {
+		s.listeners = make(map[*quic.Listener]struct{})
+	}
+	s.listeners[l] = struct{}{}
+	s.mutex.Unlock()
+}
+
+func (s *Server) removeListener(l *quic.Listener) {
+	s.mutex.Lock()
+	delete(s.listeners, l)
+	s.mutex.Unlock()
+}
+
+func (s *Server) handleConn(sess quic.Session) {
+	// TODO: accept control streams
+	decoder := qpack.NewDecoder(nil)
+
+	// send a SETTINGS frame
+	str, err := sess.OpenUniStream()
+	if err != nil {
+		s.logger.Debugf("Opening the control stream failed.")
+		return
+	}
+	buf := bytes.NewBuffer([]byte{0})
+	(&settingsFrame{}).Write(buf)
+	str.Write(buf.Bytes())
+
+	for {
+		str, err := sess.AcceptStream(context.Background())
+		if err != nil {
+			s.logger.Debugf("Accepting stream failed: %s", err)
+			return
+		}
+		go func() {
+			// [Psiphon]
+			//defer ginkgo.GinkgoRecover()
+			rerr := s.handleRequest(str, decoder, func() {
+				sess.CloseWithError(quic.ErrorCode(errorFrameUnexpected), "")
+			})
+			if rerr.err != nil || rerr.streamErr != 0 || rerr.connErr != 0 {
+				s.logger.Debugf("Handling request failed: %s", err)
+				if rerr.streamErr != 0 {
+					str.CancelWrite(quic.ErrorCode(rerr.streamErr))
+				}
+				if rerr.connErr != 0 {
+					var reason string
+					if rerr.err != nil {
+						reason = rerr.err.Error()
+					}
+					sess.CloseWithError(quic.ErrorCode(rerr.connErr), reason)
+				}
+				return
+			}
+			str.Close()
+		}()
+	}
+}
+
+func (s *Server) maxHeaderBytes() uint64 {
+	if s.Server.MaxHeaderBytes <= 0 {
+		return http.DefaultMaxHeaderBytes
+	}
+	return uint64(s.Server.MaxHeaderBytes)
+}
+
+func (s *Server) handleRequest(str quic.Stream, decoder *qpack.Decoder, onFrameError func()) requestError {
+	frame, err := parseNextFrame(str)
+	if err != nil {
+		return newStreamError(errorRequestIncomplete, err)
+	}
+	hf, ok := frame.(*headersFrame)
+	if !ok {
+		return newConnError(errorFrameUnexpected, errors.New("expected first frame to be a HEADERS frame"))
+	}
+	if hf.Length > s.maxHeaderBytes() {
+		return newStreamError(errorFrameError, fmt.Errorf("HEADERS frame too large: %d bytes (max: %d)", hf.Length, s.maxHeaderBytes()))
+	}
+	headerBlock := make([]byte, hf.Length)
+	if _, err := io.ReadFull(str, headerBlock); err != nil {
+		return newStreamError(errorRequestIncomplete, err)
+	}
+	hfs, err := decoder.DecodeFull(headerBlock)
+	if err != nil {
+		// TODO: use the right error code
+		return newConnError(errorGeneralProtocolError, err)
+	}
+	req, err := requestFromHeaders(hfs)
+	if err != nil {
+		// TODO: use the right error code
+		return newStreamError(errorGeneralProtocolError, err)
+	}
+	req.Body = newRequestBody(str, onFrameError)
+
+	if s.logger.Debug() {
+		s.logger.Infof("%s %s%s, on stream %d", req.Method, req.Host, req.RequestURI, str.StreamID())
+	} else {
+		s.logger.Infof("%s %s%s", req.Method, req.Host, req.RequestURI)
+	}
+
+	req = req.WithContext(str.Context())
+	responseWriter := newResponseWriter(str, s.logger)
+	handler := s.Handler
+	if handler == nil {
+		handler = http.DefaultServeMux
+	}
+
+	var panicked, readEOF bool
+	func() {
+		defer func() {
+			if p := recover(); p != nil {
+				// Copied from net/http/server.go
+				const size = 64 << 10
+				buf := make([]byte, size)
+				buf = buf[:runtime.Stack(buf, false)]
+				s.logger.Errorf("http: panic serving: %v\n%s", p, buf)
+				panicked = true
+			}
+		}()
+		handler.ServeHTTP(responseWriter, req)
+		// read the eof
+		if _, err = str.Read([]byte{0}); err == io.EOF {
+			readEOF = true
+		}
+	}()
+
+	if panicked {
+		responseWriter.WriteHeader(500)
+	} else {
+		responseWriter.WriteHeader(200)
+	}
+
+	if !readEOF {
+		str.CancelRead(quic.ErrorCode(errorEarlyResponse))
+	}
+	return requestError{}
+}
+
+// Close the server immediately, aborting requests and sending CONNECTION_CLOSE frames to connected clients.
+// Close in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established.
+func (s *Server) Close() error {
+	s.closed.Set(true)
+
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+
+	var err error
+	for ln := range s.listeners {
+		if cerr := (*ln).Close(); cerr != nil && err == nil {
+			err = cerr
+		}
+	}
+	return err
+}
+
+// CloseGracefully shuts down the server gracefully. The server sends a GOAWAY frame first, then waits for either timeout to trigger, or for all running requests to complete.
+// CloseGracefully in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established.
+func (s *Server) CloseGracefully(timeout time.Duration) error {
+	// TODO: implement
+	return nil
+}
+
+// SetQuicHeaders can be used to set the proper headers that announce that this server supports QUIC.
+// The values that are set depend on the port information from s.Server.Addr, and currently look like this (if Addr has port 443):
+//  Alt-Svc: quic=":443"; ma=2592000; v="33,32,31,30"
+func (s *Server) SetQuicHeaders(hdr http.Header) error {
+	port := atomic.LoadUint32(&s.port)
+
+	if port == 0 {
+		// Extract port from s.Server.Addr
+		_, portStr, err := net.SplitHostPort(s.Server.Addr)
+		if err != nil {
+			return err
+		}
+		portInt, err := net.LookupPort("tcp", portStr)
+		if err != nil {
+			return err
+		}
+		port = uint32(portInt)
+		atomic.StoreUint32(&s.port, port)
+	}
+
+	hdr.Add("Alt-Svc", fmt.Sprintf(`%s=":%d"; ma=2592000`, nextProtoH3, port))
+
+	return nil
+}
+
+// ListenAndServeQUIC listens on the UDP network address addr and calls the
+// handler for HTTP/3 requests on incoming connections. http.DefaultServeMux is
+// used when handler is nil.
+func ListenAndServeQUIC(addr, certFile, keyFile string, handler http.Handler) error {
+	server := &Server{
+		Server: &http.Server{
+			Addr:    addr,
+			Handler: handler,
+		},
+	}
+	return server.ListenAndServeTLS(certFile, keyFile)
+}
+
+// ListenAndServe listens on the given network address for both, TLS and QUIC
+// connetions 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.
+func ListenAndServe(addr, certFile, keyFile string, handler http.Handler) error {
+	// Load certs
+	var err error
+	certs := make([]tls.Certificate, 1)
+	certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
+	if err != nil {
+		return err
+	}
+	// We currently only use the cert-related stuff from tls.Config,
+	// so we don't need to make a full copy.
+	config := &tls.Config{
+		Certificates: certs,
+	}
+
+	// Open the listeners
+	udpAddr, err := net.ResolveUDPAddr("udp", addr)
+	if err != nil {
+		return err
+	}
+	udpConn, err := net.ListenUDP("udp", udpAddr)
+	if err != nil {
+		return err
+	}
+	defer udpConn.Close()
+
+	tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
+	if err != nil {
+		return err
+	}
+	tcpConn, err := net.ListenTCP("tcp", tcpAddr)
+	if err != nil {
+		return err
+	}
+	defer tcpConn.Close()
+
+	tlsConn := tls.NewListener(tcpConn, config)
+	defer tlsConn.Close()
+
+	// Start the servers
+	httpServer := &http.Server{
+		Addr:      addr,
+		TLSConfig: config,
+	}
+
+	quicServer := &Server{
+		Server: httpServer,
+	}
+
+	if handler == nil {
+		handler = http.DefaultServeMux
+	}
+	httpServer.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		quicServer.SetQuicHeaders(w.Header())
+		handler.ServeHTTP(w, r)
+	})
+
+	hErr := make(chan error)
+	qErr := make(chan error)
+	go func() {
+		hErr <- httpServer.Serve(tlsConn)
+	}()
+	go func() {
+		qErr <- quicServer.Serve(udpConn)
+	}()
+
+	select {
+	case err := <-hErr:
+		quicServer.Close()
+		return err
+	case err := <-qErr:
+		// Cannot close the HTTP server or wait for requests to complete properly :/
+		return err
+	}
+}

+ 28 - 22
vendor/vendor.json

@@ -65,68 +65,74 @@
 		{
 			"checksumSHA1": "oFGg863D73zxq4gEcS8ymUDNCvg=",
 			"path": "github.com/Psiphon-Labs/quic-go",
-			"revision": "a22a057e030897668d1b2bcca574affc2b6c3e73",
-			"revisionTime": "2019-12-09T17:49:17Z"
+			"revision": "faf3e19a7a103c823c22558bf285d40fcb957f64",
+			"revisionTime": "2019-12-09T18:11:26Z"
+		},
+		{
+			"checksumSHA1": "VMJLFpeoJ56PTQxR0wEkkiQTr1s=",
+			"path": "github.com/Psiphon-Labs/quic-go/http3",
+			"revision": "faf3e19a7a103c823c22558bf285d40fcb957f64",
+			"revisionTime": "2019-12-09T18:11:26Z"
 		},
 		{
 			"checksumSHA1": "arNC0xzgLc4ZYyItIN7phkPKtek=",
 			"path": "github.com/Psiphon-Labs/quic-go/internal/ackhandler",
-			"revision": "a22a057e030897668d1b2bcca574affc2b6c3e73",
-			"revisionTime": "2019-12-09T17:49:17Z"
+			"revision": "faf3e19a7a103c823c22558bf285d40fcb957f64",
+			"revisionTime": "2019-12-09T18:11:26Z"
 		},
 		{
 			"checksumSHA1": "umHqolZ17yo8VoXycC9eRvlbkO8=",
 			"path": "github.com/Psiphon-Labs/quic-go/internal/congestion",
-			"revision": "a22a057e030897668d1b2bcca574affc2b6c3e73",
-			"revisionTime": "2019-12-09T17:49:17Z"
+			"revision": "faf3e19a7a103c823c22558bf285d40fcb957f64",
+			"revisionTime": "2019-12-09T18:11:26Z"
 		},
 		{
 			"checksumSHA1": "l2U3fJvz7V2qSonhmdu5jzjRiVA=",
 			"path": "github.com/Psiphon-Labs/quic-go/internal/flowcontrol",
-			"revision": "a22a057e030897668d1b2bcca574affc2b6c3e73",
-			"revisionTime": "2019-12-09T17:49:17Z"
+			"revision": "faf3e19a7a103c823c22558bf285d40fcb957f64",
+			"revisionTime": "2019-12-09T18:11:26Z"
 		},
 		{
 			"checksumSHA1": "+kFrUAIuBtByAQvoimG1qtAk7OA=",
 			"path": "github.com/Psiphon-Labs/quic-go/internal/handshake",
-			"revision": "a22a057e030897668d1b2bcca574affc2b6c3e73",
-			"revisionTime": "2019-12-09T17:49:17Z"
+			"revision": "faf3e19a7a103c823c22558bf285d40fcb957f64",
+			"revisionTime": "2019-12-09T18:11:26Z"
 		},
 		{
 			"checksumSHA1": "lwlfjOQ34xJJDCFm23T3tGBz2Qk=",
 			"path": "github.com/Psiphon-Labs/quic-go/internal/protocol",
-			"revision": "a22a057e030897668d1b2bcca574affc2b6c3e73",
-			"revisionTime": "2019-12-09T17:49:17Z"
+			"revision": "faf3e19a7a103c823c22558bf285d40fcb957f64",
+			"revisionTime": "2019-12-09T18:11:26Z"
 		},
 		{
 			"checksumSHA1": "RfG1431vMrJq9QLoTw82iqKM60I=",
 			"path": "github.com/Psiphon-Labs/quic-go/internal/qerr",
-			"revision": "a22a057e030897668d1b2bcca574affc2b6c3e73",
-			"revisionTime": "2019-12-09T17:49:17Z"
+			"revision": "faf3e19a7a103c823c22558bf285d40fcb957f64",
+			"revisionTime": "2019-12-09T18:11:26Z"
 		},
 		{
 			"checksumSHA1": "yE18mLWfFblPiprkNDVm/OZBksA=",
 			"path": "github.com/Psiphon-Labs/quic-go/internal/utils",
-			"revision": "a22a057e030897668d1b2bcca574affc2b6c3e73",
-			"revisionTime": "2019-12-09T17:49:17Z"
+			"revision": "faf3e19a7a103c823c22558bf285d40fcb957f64",
+			"revisionTime": "2019-12-09T18:11:26Z"
 		},
 		{
 			"checksumSHA1": "IHqcabVUhdoVAeaj7yxH1Khu3+o=",
 			"path": "github.com/Psiphon-Labs/quic-go/internal/wire",
-			"revision": "a22a057e030897668d1b2bcca574affc2b6c3e73",
-			"revisionTime": "2019-12-09T17:49:17Z"
+			"revision": "faf3e19a7a103c823c22558bf285d40fcb957f64",
+			"revisionTime": "2019-12-09T18:11:26Z"
 		},
 		{
 			"checksumSHA1": "GazFEGFAJrK5DThTfhgeVQSokeY=",
 			"path": "github.com/Psiphon-Labs/quic-go/quictrace",
-			"revision": "a22a057e030897668d1b2bcca574affc2b6c3e73",
-			"revisionTime": "2019-12-09T17:49:17Z"
+			"revision": "faf3e19a7a103c823c22558bf285d40fcb957f64",
+			"revisionTime": "2019-12-09T18:11:26Z"
 		},
 		{
 			"checksumSHA1": "UdV2crvbY4riGxH6tn0dNrT40b0=",
 			"path": "github.com/Psiphon-Labs/quic-go/quictrace/pb",
-			"revision": "a22a057e030897668d1b2bcca574affc2b6c3e73",
-			"revisionTime": "2019-12-09T17:49:17Z"
+			"revision": "faf3e19a7a103c823c22558bf285d40fcb957f64",
+			"revisionTime": "2019-12-09T18:11:26Z"
 		},
 		{
 			"checksumSHA1": "+lsQUKG+zIU9IxrQf5pJBSRE79Q=",