Browse Source

Re-apply quic-go gquic branch local patches

Previous commits:

- https://github.com/Psiphon-Labs/psiphon-tunnel-core/commit/35eb3c1b3dc60ab80a8df646659f6bd3a4eade98

- https://github.com/Psiphon-Labs/psiphon-tunnel-core/commit/68432a251f44a7c85210b5598646666182391d4d

- https://github.com/Psiphon-Labs/psiphon-tunnel-core/commit/9a26991032511233b483be7ae6d6cc73bd4ee082
Rod Hynes 6 years ago
parent
commit
98b4ee80fb

+ 26 - 6
psiphon/common/quic/gquic-go/client.go

@@ -124,14 +124,23 @@ func dialContext(
 	config *Config,
 	createdPacketConn bool,
 ) (Session, error) {
-	config = populateClientConfig(config, createdPacketConn)
-	if !createdPacketConn {
-		for _, v := range config.Versions {
-			if v == protocol.Version44 {
-				return nil, errors.New("Cannot multiplex connections using gQUIC 44, see https://groups.google.com/a/chromium.org/forum/#!topic/proto-quic/pE9NlLLjizE. Please disable gQUIC 44 in the quic.Config, or use DialAddr")
+	// [Psiphon]
+	// We call DialContext as we need to create a custom net.PacketConn.
+	// There is one custom net.PacketConn per QUIC connection, which
+	// satisfies the gQUIC 44 constraint.
+	config = populateClientConfig(config, true)
+	/*
+			config = populateClientConfig(config, createdPacketConn)
+			if !createdPacketConn {
+				for _, v := range config.Versions {
+					if v == protocol.Version44 {
+						return nil, errors.New("Cannot multiplex connections using gQUIC 44, see https://groups.google.com/a/chromium.org/forum/#!topic/proto-quic/pE9NlLLjizE. Please disable gQUIC 44 in the quic.Config, or use DialAddr")
+					}
+				}
 			}
 		}
-	}
+	*/
+	// [Psiphon]
 	packetHandlers, err := getMultiplexer().AddConn(pconn, config.ConnectionIDLength)
 	if err != nil {
 		return nil, err
@@ -407,6 +416,17 @@ func (c *client) handlePacketImpl(p *receivedPacket) error {
 			c.handleRetryPacket(p.header)
 			return nil
 		case protocol.PacketTypeHandshake, protocol.PacketType0RTT:
+
+		// [Psiphon]
+		// The fix in https://github.com/lucas-clemente/quic-go/commit/386b77f422028fe86aae7ae9c017ca2c692c8184 must
+		// also be applied here.
+		case protocol.PacketTypeInitial:
+			if p.header.Version == protocol.Version44 {
+				break
+			}
+			fallthrough
+		// [Psiphon]
+
 		default:
 			return fmt.Errorf("Received unsupported packet type: %s", p.header.Type)
 		}

+ 50 - 7
psiphon/common/quic/gquic-go/h2quic/client.go

@@ -39,7 +39,14 @@ type client struct {
 	dialOnce     sync.Once
 	dialer       func(network, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.Session, error)
 
-	session       quic.Session
+	// [Psiphon]
+	// Fix close-while-dialing race condition by synchronizing access to
+	// client.session and adding a closed flag to indicate if the client was
+	// closed while a dial was in progress.
+	sessionMutex sync.Mutex
+	closed       bool
+	session      quic.Session
+
 	headerStream  quic.Stream
 	headerErr     *qerr.QuicError
 	headerErrored chan struct{} // this channel is closed if an error occurs on the header stream
@@ -84,15 +91,35 @@ func newClient(
 // dial dials the connection
 func (c *client) dial() error {
 	var err error
+
+	// [Psiphon]
+	var session quic.Session
+
 	if c.dialer != nil {
-		c.session, err = c.dialer("udp", c.hostname, c.tlsConf, c.config)
+		session, err = c.dialer("udp", c.hostname, c.tlsConf, c.config)
 	} else {
-		c.session, err = dialAddr(c.hostname, c.tlsConf, c.config)
+		session, err = dialAddr(c.hostname, c.tlsConf, c.config)
 	}
 	if err != nil {
 		return err
 	}
 
+	// [Psiphon]
+	// Only this write and the Close reads of c.session require synchronization.
+	// After this point, it's safe to concurrently read c.session as it is not
+	// rewritten.
+	c.sessionMutex.Lock()
+	closed := c.closed
+	if !closed {
+		c.session = session
+	}
+	c.sessionMutex.Unlock()
+	if closed {
+		session.Close()
+		return errors.New("closed while dialing")
+	}
+	// [Psiphon]
+
 	// once the version has been negotiated, open the header stream
 	c.headerStream, err = c.session.OpenStream()
 	if err != nil {
@@ -276,18 +303,34 @@ func (c *client) writeRequestBody(dataStream quic.Stream, body io.ReadCloser) (e
 }
 
 func (c *client) closeWithError(e error) error {
-	if c.session == nil {
+
+	// [Psiphon]
+	c.sessionMutex.Lock()
+	session := c.session
+	c.closed = true
+	c.sessionMutex.Unlock()
+	// [Psiphon]
+
+	if session == nil {
 		return nil
 	}
-	return c.session.CloseWithError(quic.ErrorCode(qerr.InternalError), e)
+	return session.CloseWithError(quic.ErrorCode(qerr.InternalError), e)
 }
 
 // Close closes the client
 func (c *client) Close() error {
-	if c.session == nil {
+
+	// [Psiphon]
+	c.sessionMutex.Lock()
+	session := c.session
+	c.closed = true
+	c.sessionMutex.Unlock()
+	// [Psiphon]
+
+	if session == nil {
 		return nil
 	}
-	return c.session.Close()
+	return session.Close()
 }
 
 // copied from net/transport.go

+ 15 - 0
psiphon/common/quic/gquic-go/internal/handshake/crypto_setup_server.go

@@ -203,6 +203,14 @@ func (h *cryptoSetupServer) Open(dst, src []byte, packetNumber protocol.PacketNu
 	defer h.mutex.RUnlock()
 
 	if h.forwardSecureAEAD != nil {
+
+		// [Psiphon]
+		// Apply pending upstream fix: https://github.com/lucas-clemente/quic-go/commit/ffd84b0caad3d0214d0f407646c41dd140c22e8a.
+		if !h.receivedForwardSecurePacket {
+			dst = []byte{}
+		}
+		// [Psiphon]
+
 		res, err := h.forwardSecureAEAD.Open(dst, src, packetNumber, associatedData)
 		if err == nil {
 			if !h.receivedForwardSecurePacket { // this is the first forward secure packet we receive from the client
@@ -219,6 +227,13 @@ func (h *cryptoSetupServer) Open(dst, src []byte, packetNumber protocol.PacketNu
 		}
 	}
 	if h.secureAEAD != nil {
+
+		// [Psiphon]
+		if !h.receivedSecurePacket {
+			dst = []byte{}
+		}
+		// [Psiphon]
+
 		res, err := h.secureAEAD.Open(dst, src, packetNumber, associatedData)
 		if err == nil {
 			h.logger.Debugf("Received first secure packet. Stopping to accept unencrypted packets.")

+ 13 - 0
psiphon/common/quic/gquic-go/packet_packer_legacy.go

@@ -417,6 +417,19 @@ func (p *packetPackerLegacy) writeAndSealPacket(
 	}
 	payloadStartIndex := buffer.Len()
 
+	// [Psiphon]
+	// In our tests, gQUICv44 works only when we restore this block of code that was refactored out in:
+	// https://github.com/lucas-clemente/quic-go/commit/5df98dc3891df7349ee6ff41714fbe6bb4d72440.
+
+	// the Initial packet needs to be padded, so the last STREAM frame must have the data length present
+	if header.Type == protocol.PacketTypeInitial {
+		lastFrame := frames[len(frames)-1]
+		if sf, ok := lastFrame.(*wire.StreamFrame); ok {
+			sf.DataLenPresent = true
+		}
+	}
+	// [Psiphon]
+
 	for _, frame := range frames {
 		if err := frame.Write(buffer, p.version); err != nil {
 			return nil, err

+ 9 - 1
psiphon/common/quic/gquic-go/receive_stream.go

@@ -270,7 +270,15 @@ func (s *receiveStream) CloseRemote(offset protocol.ByteCount) {
 }
 
 func (s *receiveStream) onClose(offset protocol.ByteCount) {
-	if s.canceledRead && !s.version.UsesIETFFrameFormat() {
+
+	// [Psiphon]
+	// Fix race condition.
+	s.mutex.Lock()
+	canceledRead := s.canceledRead
+	s.mutex.Unlock()
+	// [Psiphon]
+
+	if canceledRead && !s.version.UsesIETFFrameFormat() {
 		s.sender.queueControlFrame(&wire.RstStreamFrame{
 			StreamID:   s.streamID,
 			ByteOffset: offset,