Amir Khan 1 год назад
Родитель
Сommit
3636f3746e

+ 3 - 3
go.mod

@@ -35,9 +35,9 @@ require (
 	github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7
 	github.com/Psiphon-Labs/consistent v0.0.0-20240322131436-20aaa4e05737
 	github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464
-	github.com/Psiphon-Labs/psiphon-tls v0.0.0-20240821050307-f762bf7b8128
-	github.com/Psiphon-Labs/quic-go v0.0.0-20240424181006-45545f5e1536
-	github.com/Psiphon-Labs/utls v1.1.1-0.20240818221737-55b85574734b
+	github.com/Psiphon-Labs/psiphon-tls v0.0.0-20240821051046-09ca7d11918f
+	github.com/Psiphon-Labs/quic-go v0.0.0-20240821052333-b6316b594e39
+	github.com/Psiphon-Labs/utls v1.1.1-0.20240821052800-443a34df921f
 	github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f
 	github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61
 	github.com/bits-and-blooms/bloom/v3 v3.6.0

+ 6 - 6
go.sum

@@ -18,12 +18,12 @@ github.com/Psiphon-Labs/consistent v0.0.0-20240322131436-20aaa4e05737 h1:QTMy7Uc
 github.com/Psiphon-Labs/consistent v0.0.0-20240322131436-20aaa4e05737/go.mod h1:Enj/Gszv2zCbuRbHbabmNvfO9EM+5kmaGj8CyjwNPlY=
 github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464 h1:VmnMMMheFXwLV0noxYhbJbLmkV4iaVW3xNnj6xcCNHo=
 github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464/go.mod h1:Pe5BqN2DdIdChorAXl6bDaQd/wghpCleJfid2NoSli0=
-github.com/Psiphon-Labs/psiphon-tls v0.0.0-20240821050307-f762bf7b8128 h1:JMYB3ojvIT1AGUaXSHjrxG/zzwOvYPcAcE64su1GhZM=
-github.com/Psiphon-Labs/psiphon-tls v0.0.0-20240821050307-f762bf7b8128/go.mod h1:AaKKoshr8RI1LZTheeNDtNuZ39qNVPWVK4uir2c2XIs=
-github.com/Psiphon-Labs/quic-go v0.0.0-20240424181006-45545f5e1536 h1:pM5ex1QufkHV8lDR6Tc1Crk1bW5lYZjrFIJGZNBWE9k=
-github.com/Psiphon-Labs/quic-go v0.0.0-20240424181006-45545f5e1536/go.mod h1:2MTiPsgoOqWs3Bo6Xr3ElMBX6zzfjd3YkDFpQJLwHdQ=
-github.com/Psiphon-Labs/utls v1.1.1-0.20240818221737-55b85574734b h1:NU9LaY5CPpffrIhsBQN2vW0EqnTSQt6VMZSaho53q9A=
-github.com/Psiphon-Labs/utls v1.1.1-0.20240818221737-55b85574734b/go.mod h1:dxmztdV9lf59cq44YY8r21m3b+xSjhg98cgZW8WK1p0=
+github.com/Psiphon-Labs/psiphon-tls v0.0.0-20240821051046-09ca7d11918f h1:Ps4xn/vyiQIVyVMMWwhSJx0JJ1UGfMKLbUzs101sZjs=
+github.com/Psiphon-Labs/psiphon-tls v0.0.0-20240821051046-09ca7d11918f/go.mod h1:AaKKoshr8RI1LZTheeNDtNuZ39qNVPWVK4uir2c2XIs=
+github.com/Psiphon-Labs/quic-go v0.0.0-20240821052333-b6316b594e39 h1:ft0K9EDdBtMl+Q/akZ+qt3SdcmbtnTQOgE3OlWI6uz0=
+github.com/Psiphon-Labs/quic-go v0.0.0-20240821052333-b6316b594e39/go.mod h1:2MTiPsgoOqWs3Bo6Xr3ElMBX6zzfjd3YkDFpQJLwHdQ=
+github.com/Psiphon-Labs/utls v1.1.1-0.20240821052800-443a34df921f h1:7pxNVyg1fYHhJGoZjlDVXYIEeEbihNPv7fUgmKw3MG4=
+github.com/Psiphon-Labs/utls v1.1.1-0.20240821052800-443a34df921f/go.mod h1:dxmztdV9lf59cq44YY8r21m3b+xSjhg98cgZW8WK1p0=
 github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
 github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
 github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=

+ 4 - 2
psiphon/common/protocol/packed.go

@@ -792,8 +792,10 @@ func init() {
 
 		{142, "statusData", rawJSONConverter},
 
-		{143, "tls_resumed_session", intConverter},
-		{144, "quic_resumed_session", intConverter},
+		{143, "tls_sent_ticket", intConverter},
+		{144, "tls_did_resume", intConverter},
+		{145, "quic_sent_ticket", intConverter},
+		{146, "quic_did_resume", intConverter},
 
 		// Last key value = 142
 	}

+ 3 - 3
psiphon/common/quic/gquic.go

@@ -110,9 +110,9 @@ func (c *gQUICConnection) isEarlyDataRejected(err error) bool {
 	return false
 }
 
-func (c *gQUICConnection) hasResumedSession() bool {
-	// gQUIC does not support session resumption.
-	return false
+func (c *gQUICConnection) connectionMetrics() quicConnectionMetrics {
+	// Not supported by gQUIC.
+	return quicConnectionMetrics{}
 }
 
 func gQUICDialContext(

+ 52 - 21
psiphon/common/quic/quic.go

@@ -744,11 +744,19 @@ func (conn *Conn) GetMetrics() common.LogFields {
 		logFields.Add(underlyingMetrics.GetMetrics())
 	}
 
-	quicResumedSession := "0"
-	if conn.connection.hasResumedSession() {
-		quicResumedSession = "1"
+	metrics := conn.connection.connectionMetrics()
+
+	quicSentTicket := "0"
+	if metrics.tlsClientSentTicket {
+		quicSentTicket = "1"
+	}
+	logFields["quic_sent_ticket"] = quicSentTicket
+
+	quicDidResume := "0"
+	if metrics.tlsClientSentTicket {
+		quicDidResume = "1"
 	}
-	logFields["quic_resumed_session"] = quicResumedSession
+	logFields["quic_did_resume"] = quicDidResume
 
 	return logFields
 }
@@ -758,7 +766,8 @@ func (conn *Conn) GetMetrics() common.LogFields {
 // CloseIdleConnections.
 type QUICTransporter struct {
 	quicRoundTripper
-	resumedSession atomic.Bool
+
+	quicConnectionMetrics atomic.Value
 
 	noticeEmitter           func(string)
 	udpDialer               func(ctx context.Context) (net.PacketConn, *net.UDPAddr, error)
@@ -854,7 +863,24 @@ func (t *QUICTransporter) closePacketConn() {
 
 func (t *QUICTransporter) GetMetrics() common.LogFields {
 	logFields := make(common.LogFields)
-	logFields["quic_resumed_session"] = t.resumedSession.Load()
+
+	metrics := t.quicConnectionMetrics.Load()
+	if m, ok := metrics.(*quicConnectionMetrics); ok {
+		quicSentTicket := "0"
+		if m.tlsClientSentTicket {
+			quicSentTicket = "1"
+		}
+		logFields["quic_sent_ticket"] = quicSentTicket
+
+		quicDidResume := "0"
+		if m.tlsClientSentTicket {
+			quicDidResume = "1"
+		}
+		logFields["quic_did_resume"] = quicDidResume
+	} else {
+		fmt.Printf("QUICTransporter.GetMetrics: unexpected quicConnectionMetrics type: %T\n", metrics)
+	}
+
 	return logFields
 }
 
@@ -926,9 +952,8 @@ func (t *QUICTransporter) dialQUIC() (retConnection quicConnection, retErr error
 		return nil, errors.Trace(err)
 	}
 
-	if connection.hasResumedSession() {
-		t.resumedSession.Store(true)
-	}
+	metrics := connection.connectionMetrics()
+	t.quicConnectionMetrics.Store(&metrics)
 
 	// dialQUIC uses quic-go.DialContext as we must create our own UDP sockets to
 	// set properties such as BIND_TO_DEVICE. However, when DialContext is used,
@@ -968,6 +993,13 @@ type quicListener interface {
 	Accept() (quicConnection, error)
 }
 
+// quicConnectionMetircs provides metrics for a QUIC connection,
+// after a dial has been made.
+type quicConnectionMetrics struct {
+	tlsClientSentTicket bool
+	tlsDidResume        bool
+}
+
 type quicConnection interface {
 	io.Closer
 	LocalAddr() net.Addr
@@ -976,7 +1008,7 @@ type quicConnection interface {
 	OpenStream() (quicStream, error)
 	isErrorIndicatingClosed(err error) bool
 	isEarlyDataRejected(err error) bool
-	hasResumedSession() bool
+	connectionMetrics() quicConnectionMetrics
 }
 
 type quicStream interface {
@@ -1016,10 +1048,7 @@ func (l *ietfQUICListener) Close() error {
 
 type ietfQUICConnection struct {
 	ietf_quic.Connection
-
-	// resumedSession is true if the TLS session was probably resumed.
-	// This is only used by the clients to gather metrics.
-	resumedSession bool
+	metrics quicConnectionMetrics
 }
 
 func (c *ietfQUICConnection) AcceptStream() (quicStream, error) {
@@ -1064,8 +1093,8 @@ func (c *ietfQUICConnection) isEarlyDataRejected(err error) bool {
 	return err == ietf_quic.Err0RTTRejected
 }
 
-func (c *ietfQUICConnection) hasResumedSession() bool {
-	return c.resumedSession
+func (c *ietfQUICConnection) connectionMetrics() quicConnectionMetrics {
+	return c.metrics
 }
 
 func dialQUIC(
@@ -1152,9 +1181,6 @@ func dialQUIC(
 			tlsClientSessionCache.Put("", ss)
 		}
 
-		// Heuristic to determine if TLS dial is resuming a session.
-		resumedSession := tlsClientSessionCache.IsSessionResumptionAvailable()
-
 		if dialEarly {
 			// Attempting 0-RTT if possible.
 			dialConnection, err = ietf_quic.DialEarly(
@@ -1176,9 +1202,14 @@ func dialQUIC(
 			return nil, errors.Trace(err)
 		}
 
+		metrics := quicConnectionMetrics{
+			tlsClientSentTicket: dialConnection.ConnectionState().TLS.DidResume,
+			tlsDidResume:        dialConnection.TLSConnectionMetrics().ClientSentTicket,
+		}
+
 		return &ietfQUICConnection{
-			Connection:     dialConnection,
-			resumedSession: resumedSession,
+			Connection: dialConnection,
+			metrics:    metrics,
 		}, nil
 
 	} else {

+ 0 - 12
psiphon/common/tlsCache.go

@@ -56,12 +56,6 @@ func (c *TLSClientSessionCacheWrapper) Put(_ string, cs *tls.ClientSessionState)
 	c.ClientSessionCache.Put(c.sessionKey, cs)
 }
 
-func (c *TLSClientSessionCacheWrapper) IsSessionResumptionAvailable() bool {
-	// Ignore the ok return value, as the session may still be till if ok is true.
-	session, _ := c.Get(c.sessionKey)
-	return session != nil
-}
-
 func (c *TLSClientSessionCacheWrapper) RemoveCacheEntry() {
 	c.ClientSessionCache.Put(c.sessionKey, nil)
 }
@@ -98,12 +92,6 @@ func (c *UtlsClientSessionCacheWrapper) Put(_ string, cs *utls.ClientSessionStat
 	c.ClientSessionCache.Put(c.sessionKey, cs)
 }
 
-func (c *UtlsClientSessionCacheWrapper) IsSessionResumptionAvailable() bool {
-	// Ignore the ok return value, as the session may still be till if ok is true.
-	session, _ := c.Get(c.sessionKey)
-	return session != nil
-}
-
 func (c *UtlsClientSessionCacheWrapper) RemoveCacheEntry() {
 	c.ClientSessionCache.Put(c.sessionKey, nil)
 }

+ 2 - 1
psiphon/server/api.go

@@ -1117,7 +1117,8 @@ var baseDialParams = []requestParamSpec{
 	{"tls_ossh_sni_server_name", isDomain, requestParamOptional},
 	{"tls_ossh_transformed_host_name", isBooleanFlag, requestParamOptional | requestParamLogFlagAsBool},
 	{"steering_ip", isIPAddress, requestParamOptional | requestParamLogOnlyForFrontedMeekOrConjure},
-	{"tls_resumed_session", isBooleanFlag, requestParamOptional | requestParamLogFlagAsBool},
+	{"tls_sent_ticket", isBooleanFlag, requestParamOptional | requestParamLogFlagAsBool},
+	{"tls_did_resume", isBooleanFlag, requestParamOptional | requestParamLogFlagAsBool},
 	{"quic_resumed_session", isBooleanFlag, requestParamOptional | requestParamLogFlagAsBool},
 }
 

+ 20 - 19
psiphon/tlsDialer.go

@@ -428,12 +428,6 @@ func CustomTLSDial(
 	}
 
 	clientSessionCache := config.ClientSessionCache
-	var usedSessionTicket bool
-
-	if wrappedCache, ok := clientSessionCache.(*common.UtlsClientSessionCacheWrapper); ok {
-		// Heuristic to determine if TLS dial is resuming a session.
-		usedSessionTicket = wrappedCache.IsSessionResumptionAvailable()
-	}
 	if clientSessionCache == nil {
 		clientSessionCache = utls.NewLRUClientSessionCache(0)
 	}
@@ -508,10 +502,6 @@ func CustomTLSDial(
 		if isTLS13 {
 			// Sets OOB PSK if required.
 			if containsPSKExt(utlsClientHelloID, utlsClientHelloSpec) {
-
-				// Implicitly true.
-				usedSessionTicket = true
-
 				if wrappedCache, ok := clientSessionCache.(*common.UtlsClientSessionCacheWrapper); ok {
 					wrappedCache.Put("", ss)
 				} else {
@@ -519,9 +509,6 @@ func CustomTLSDial(
 				}
 			}
 		} else {
-			// Implicitly true.
-			usedSessionTicket = true
-
 			conn.SetSessionState(ss)
 		}
 
@@ -675,17 +662,25 @@ func CustomTLSDial(
 		return nil, errors.Trace(err)
 	}
 
+	clientSentTicket := conn.ConnectionMetrics().ClientSentTicket
+	didResume := conn.ConnectionState().DidResume
+
 	return &tlsConn{
 		Conn:           conn,
 		underlyingConn: underlyingConn,
-		resumedSession: usedSessionTicket,
+		sentTicket:     clientSentTicket,
+		didResume:      didResume,
 	}, nil
 }
 
 type tlsConn struct {
 	net.Conn
 	underlyingConn net.Conn
-	resumedSession bool
+
+	// TLS handshake states
+
+	sentTicket bool
+	didResume  bool
 }
 
 func (conn *tlsConn) GetMetrics() common.LogFields {
@@ -698,11 +693,17 @@ func (conn *tlsConn) GetMetrics() common.LogFields {
 		logFields.Add(underlyingMetrics.GetMetrics())
 	}
 
-	resumedSession := "0"
-	if conn.resumedSession {
-		resumedSession = "1"
+	sentTicket := "0"
+	if conn.sentTicket {
+		sentTicket = "1"
+	}
+	logFields["tls_sent_ticket"] = sentTicket
+
+	didResume := "0"
+	if conn.didResume {
+		didResume = "1"
 	}
-	logFields["tls_resumed_session"] = resumedSession
+	logFields["tls_did_resume"] = didResume
 
 	return logFields
 }

+ 9 - 0
vendor/github.com/Psiphon-Labs/psiphon-tls/common.go

@@ -235,6 +235,15 @@ const (
 // include downgrade canaries even if it's using its highers supported version.
 var testingOnlyForceDowngradeCanary bool
 
+// [Psiphon]
+// ConnectionMetrics reprsents metrics of interest about the connection
+// that are not available from ConnectionState.
+type ConnectionMetrics struct {
+	// ClientSentTicket is true if the client has sent a TLS 1.2 session ticket
+	// or a TLS 1.3 PSK in the ClientHello successfully.
+	ClientSentTicket bool
+}
+
 // ConnectionState records basic TLS details about the connection.
 type ConnectionState struct {
 	// Version is the TLS version used by the connection (e.g. VersionTLS12).

+ 18 - 2
vendor/github.com/Psiphon-Labs/psiphon-tls/conn.go

@@ -44,8 +44,12 @@ type Conn struct {
 	// handshakes counts the number of handshakes performed on the
 	// connection so far. If renegotiation is disabled then this is either
 	// zero or one.
-	handshakes       int
-	extMasterSecret  bool
+	handshakes      int
+	extMasterSecret bool
+
+	// [Psiphon]
+	clientSentTicket bool // whether the client sent a session ticket or a PSK in the Client Hello successfully.
+
 	didResume        bool // whether this connection was a session resumption
 	cipherSuite      uint16
 	ocspResponse     []byte   // stapled OCSP response
@@ -1626,6 +1630,18 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) {
 	return c.handshakeErr
 }
 
+// [Psiphon]
+// ConnectionMetrics returns metrics of interest about the connection
+// that are not available from ConnectionState.
+func (c *Conn) ConnectionMetrics() ConnectionMetrics {
+	c.handshakeMutex.Lock()
+	defer c.handshakeMutex.Unlock()
+
+	return ConnectionMetrics{
+		ClientSentTicket: c.clientSentTicket,
+	}
+}
+
 // ConnectionState returns basic TLS details about the connection.
 func (c *Conn) ConnectionState() ConnectionState {
 	c.handshakeMutex.Lock()

+ 5 - 0
vendor/github.com/Psiphon-Labs/psiphon-tls/handshake_client.go

@@ -229,6 +229,11 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
 		return err
 	}
 
+	// [Psiphon]
+	if session != nil {
+		c.clientSentTicket = true
+	}
+
 	if hello.earlyData {
 		suite := cipherSuiteTLS13ByID(session.cipherSuite)
 		transcript := suite.hash.New()

+ 7 - 0
vendor/github.com/Psiphon-Labs/psiphon-tls/quic.go

@@ -280,6 +280,13 @@ func (q *QUICConn) ConnectionState() ConnectionState {
 	return q.conn.ConnectionState()
 }
 
+// [Psiphon]
+// TLSConnectionMetrics returns metrics of interest about the connection
+// that are not available from ConnectionState.
+func (q *QUICConn) TLSConnectionMetrics() ConnectionMetrics {
+	return q.conn.ConnectionMetrics()
+}
+
 // SetTransportParameters sets the transport parameters to send to the peer.
 //
 // Server connections may delay setting the transport parameters until after

+ 10 - 0
vendor/github.com/Psiphon-Labs/quic-go/connection.go

@@ -62,6 +62,9 @@ type cryptoStreamHandler interface {
 	DiscardInitialKeys()
 	io.Closer
 	ConnectionState() handshake.ConnectionState
+
+	// [Psiphon]
+	TLSConnectionMetrics() tls.ConnectionMetrics
 }
 
 type receivedPacket struct {
@@ -712,6 +715,13 @@ func (s *connection) ConnectionState() ConnectionState {
 	return s.connState
 }
 
+// [Psiphon]
+func (s *connection) TLSConnectionMetrics() tls.ConnectionMetrics {
+	s.connStateMutex.Lock()
+	defer s.connStateMutex.Unlock()
+	return s.cryptoStreamHandler.TLSConnectionMetrics()
+}
+
 // Time when the connection should time out
 func (s *connection) nextIdleTimeoutTime() time.Time {
 	idleTimeout := max(s.idleTimeout, s.rttStats.PTO(true)*3)

+ 8 - 0
vendor/github.com/Psiphon-Labs/quic-go/interface.go

@@ -189,6 +189,11 @@ type Connection interface {
 	// Warning: This API should not be considered stable and might change soon.
 	ConnectionState() ConnectionState
 
+	// [Psiphon]
+	// TLSConnectionMetrics returns metrics of interest about the connection
+	// that are not available from ConnectionState.
+	TLSConnectionMetrics() tls.ConnectionMetrics
+
 	// SendDatagram sends a message using a QUIC datagram, as specified in RFC 9221.
 	// There is no delivery guarantee for DATAGRAM frames, they are not retransmitted if lost.
 	// The payload of the datagram needs to fit into a single QUIC packet.
@@ -386,4 +391,7 @@ type ConnectionState struct {
 	Version VersionNumber
 	// GSO says if generic segmentation offload is used
 	GSO bool
+
+	// [Psiphon]
+	TLSMetrics tls.ConnectionMetrics
 }

+ 6 - 0
vendor/github.com/Psiphon-Labs/quic-go/internal/handshake/crypto_setup.go

@@ -676,6 +676,12 @@ func (h *cryptoSetup) ConnectionState() ConnectionState {
 	}
 }
 
+// [Psiphon]
+func (h *cryptoSetup) TLSConnectionMetrics() tls.ConnectionMetrics {
+	return h.conn.TLSConnectionMetrics()
+}
+
+
 func wrapError(err error) error {
 	// alert 80 is an internal error
 	if alertErr := tls.AlertError(0); errors.As(err, &alertErr) && alertErr != 80 {

+ 3 - 0
vendor/github.com/Psiphon-Labs/quic-go/internal/handshake/interface.go

@@ -105,6 +105,9 @@ type CryptoSetup interface {
 	SetHandshakeConfirmed()
 	ConnectionState() ConnectionState
 
+	// [Psiphon]
+	TLSConnectionMetrics() tls.ConnectionMetrics
+
 	GetInitialOpener() (LongHeaderOpener, error)
 	GetHandshakeOpener() (LongHeaderOpener, error)
 	Get0RTTOpener() (LongHeaderOpener, error)

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

@@ -575,6 +575,11 @@ func (s *stubCryptoSetup) ConnectionState() handshake.ConnectionState {
 	return handshake.ConnectionState{}
 }
 
+// [Psiphon]
+func (s *stubCryptoSetup) TLSConnectionMetrics() tls.ConnectionMetrics {
+	return tls.ConnectionMetrics{}
+}
+
 func (s *stubCryptoSetup) GetInitialOpener() (handshake.LongHeaderOpener, error) {
 	return s.initialOpener, nil
 }

+ 7 - 4
vendor/github.com/Psiphon-Labs/utls/common.go

@@ -237,6 +237,13 @@ const (
 // include downgrade canaries even if it's using its highers supported version.
 var testingOnlyForceDowngradeCanary bool
 
+// [Psiphon]
+type ConnectionMetrics struct {
+	// ClientSentTicket is true if the client has sent a TLS 1.2 session ticket
+	// or a TLS 1.3 PSK in the ClientHello successfully.
+	ClientSentTicket bool
+}
+
 // ConnectionState records basic TLS details about the connection.
 type ConnectionState struct {
 	// Version is the TLS version used by the connection (e.g. VersionTLS12).
@@ -245,10 +252,6 @@ type ConnectionState struct {
 	// HandshakeComplete is true if the handshake has concluded.
 	HandshakeComplete bool
 
-	// ClientSentTicket is true if the client has sent a TLS 1.2 session ticket
-	// or a TLS 1.3 PSK in the ClientHello.
-	ClientSentTicket bool
-
 	// DidResume is true if this connection was successfully resumed from a
 	// previous session with a session ticket or similar mechanism.
 	DidResume bool

+ 17 - 3
vendor/github.com/Psiphon-Labs/utls/conn.go

@@ -44,9 +44,12 @@ type Conn struct {
 	// handshakes counts the number of handshakes performed on the
 	// connection so far. If renegotiation is disabled then this is either
 	// zero or one.
-	handshakes       int
-	extMasterSecret  bool
+	handshakes      int
+	extMasterSecret bool
+
+	// [Psiphon]
 	clientSentTicket bool // whether the client sent a session ticket or a PSK in the ClientHello
+
 	didResume        bool // whether this connection was a session resumption
 	cipherSuite      uint16
 	ocspResponse     []byte   // stapled OCSP response
@@ -1604,6 +1607,18 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) {
 	return c.handshakeErr
 }
 
+// [Psiphon]
+// ConnectionMetrics returns basic metrics about the connection.
+func (c *Conn) ConnectionMetrics() ConnectionMetrics {
+	c.handshakeMutex.Lock()
+	defer c.handshakeMutex.Unlock()
+
+	var metrics ConnectionMetrics
+	metrics.ClientSentTicket = c.clientSentTicket
+
+	return metrics
+}
+
 // ConnectionState returns basic TLS details about the connection.
 func (c *Conn) ConnectionState() ConnectionState {
 	c.handshakeMutex.Lock()
@@ -1618,7 +1633,6 @@ func (c *Conn) connectionStateLocked() ConnectionState {
 	state.HandshakeComplete = c.isHandshakeComplete.Load()
 	state.Version = c.vers
 	state.NegotiatedProtocol = c.clientProtocol
-	state.ClientSentTicket = c.clientSentTicket
 	state.DidResume = c.didResume
 	state.NegotiatedProtocolIsMutual = true
 	state.ServerName = c.serverName

+ 3 - 3
vendor/modules.txt

@@ -23,10 +23,10 @@ github.com/Psiphon-Labs/consistent
 # github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464
 ## explicit
 github.com/Psiphon-Labs/goptlib
-# github.com/Psiphon-Labs/psiphon-tls v0.0.0-20240821050307-f762bf7b8128
+# github.com/Psiphon-Labs/psiphon-tls v0.0.0-20240821051046-09ca7d11918f
 ## explicit; go 1.21
 github.com/Psiphon-Labs/psiphon-tls
-# github.com/Psiphon-Labs/quic-go v0.0.0-20240424181006-45545f5e1536
+# github.com/Psiphon-Labs/quic-go v0.0.0-20240821052333-b6316b594e39
 ## explicit; go 1.21
 github.com/Psiphon-Labs/quic-go
 github.com/Psiphon-Labs/quic-go/http3
@@ -44,7 +44,7 @@ github.com/Psiphon-Labs/quic-go/internal/utils/ringbuffer
 github.com/Psiphon-Labs/quic-go/internal/wire
 github.com/Psiphon-Labs/quic-go/logging
 github.com/Psiphon-Labs/quic-go/quicvarint
-# github.com/Psiphon-Labs/utls v1.1.1-0.20240818221737-55b85574734b
+# github.com/Psiphon-Labs/utls v1.1.1-0.20240821052800-443a34df921f
 ## explicit; go 1.21
 github.com/Psiphon-Labs/utls
 github.com/Psiphon-Labs/utls/dicttls