TUN-9319: Add dynamic loading of features to connections via ConnectionOptionsSnapshot

Make sure to enforce snapshots of features and client information for each connection
so that the feature information can change in the background. This allows for new features
to only be applied to a connection if it completely disconnects and attempts a reconnect.

Updates the feature refresh time to 1 hour from previous cloudflared versions which
refreshed every 6 hours.

Closes TUN-9319
This commit is contained in:
Devin Carr
2025-05-14 20:11:05 +00:00
parent 02705c44b2
commit 3bf9217de5
14 changed files with 359 additions and 106 deletions

View File

@@ -17,6 +17,7 @@ import (
"github.com/rs/zerolog"
"golang.org/x/sync/errgroup"
"github.com/cloudflare/cloudflared/client"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/edgediscovery"
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
@@ -38,10 +39,8 @@ const (
)
type TunnelConfig struct {
ClientConfig *client.Config
GracePeriod time.Duration
ReplaceExisting bool
OSArch string
ClientID string
CloseConnOnce *sync.Once // Used to close connectedSignal no more than once
EdgeAddrs []string
Region string
@@ -72,22 +71,13 @@ type TunnelConfig struct {
DisableQUICPathMTUDiscovery bool
QUICConnectionLevelFlowControlLimit uint64
QUICStreamLevelFlowControlLimit uint64
FeatureSelector *features.FeatureSelector
}
func (c *TunnelConfig) connectionOptions(originLocalAddr string, numPreviousAttempts uint8) *pogs.ConnectionOptions {
func (c *TunnelConfig) connectionOptions(originLocalAddr string, previousAttempts uint8) *client.ConnectionOptionsSnapshot {
// attempt to parse out origin IP, but don't fail since it's informational field
host, _, _ := net.SplitHostPort(originLocalAddr)
originIP := net.ParseIP(host)
return &pogs.ConnectionOptions{
Client: c.NamedTunnel.Client,
OriginLocalIP: originIP,
ReplaceExisting: c.ReplaceExisting,
CompressionQuality: 0,
NumPreviousAttempts: numPreviousAttempts,
}
return c.ClientConfig.ConnectionOptionsSnapshot(originIP, previousAttempts)
}
func StartTunnelDaemon(
@@ -463,6 +453,8 @@ func (e *EdgeTunnelServer) serveConnection(
case connection.QUIC:
// nolint: gosec
connOptions := e.config.connectionOptions(addr.UDP.String(), uint8(backoff.Retries()))
// nolint: zerologlint
connOptions.LogFields(connLog.Logger().Debug().Uint8(connection.LogFieldConnIndex, connIndex)).Msgf("Tunnel connection options")
return e.serveQUIC(ctx,
addr.UDP.AddrPort(),
connLog,
@@ -479,6 +471,8 @@ func (e *EdgeTunnelServer) serveConnection(
// nolint: gosec
connOptions := e.config.connectionOptions(edgeConn.LocalAddr().String(), uint8(backoff.Retries()))
// nolint: zerologlint
connOptions.LogFields(connLog.Logger().Debug().Uint8(connection.LogFieldConnIndex, connIndex)).Msgf("Tunnel connection options")
if err := e.serveHTTP2(
ctx,
connLog,
@@ -508,11 +502,11 @@ func (e *EdgeTunnelServer) serveHTTP2(
ctx context.Context,
connLog *ConnAwareLogger,
tlsServerConn net.Conn,
connOptions *pogs.ConnectionOptions,
connOptions *client.ConnectionOptionsSnapshot,
controlStreamHandler connection.ControlStreamHandler,
connIndex uint8,
) error {
pqMode := e.config.FeatureSelector.PostQuantumMode()
pqMode := connOptions.FeatureSnapshot.PostQuantum
if pqMode == features.PostQuantumStrict {
return unrecoverableError{errors.New("HTTP/2 transport does not support post-quantum")}
}
@@ -550,19 +544,19 @@ func (e *EdgeTunnelServer) serveQUIC(
ctx context.Context,
edgeAddr netip.AddrPort,
connLogger *ConnAwareLogger,
connOptions *pogs.ConnectionOptions,
connOptions *client.ConnectionOptionsSnapshot,
controlStreamHandler connection.ControlStreamHandler,
connIndex uint8,
) (err error, recoverable bool) {
tlsConfig := e.config.EdgeTLSConfigs[connection.QUIC]
pqMode := e.config.FeatureSelector.PostQuantumMode()
pqMode := connOptions.FeatureSnapshot.PostQuantum
curvePref, err := curvePreference(pqMode, fips.IsFipsEnabled(), tlsConfig.CurvePreferences)
if err != nil {
return err, true
}
connLogger.Logger().Info().Msgf("Using %v as curve preferences", curvePref)
connLogger.Logger().Info().Msgf("Tunnel connection curve preferences: %v", curvePref)
tlsConfig.CurvePreferences = curvePref
@@ -600,12 +594,12 @@ func (e *EdgeTunnelServer) serveQUIC(
if err != nil {
connLogger.ConnAwareLogger().Err(err).Msgf("Failed to dial a quic connection")
e.reportErrorToSentry(err)
e.reportErrorToSentry(err, connOptions.FeatureSnapshot.PostQuantum)
return err, true
}
var datagramSessionManager connection.DatagramSessionHandler
if e.config.FeatureSelector.DatagramVersion() == features.DatagramV3 {
if connOptions.FeatureSnapshot.DatagramVersion == features.DatagramV3 {
datagramSessionManager = connection.NewDatagramV3Connection(
ctx,
conn,
@@ -672,7 +666,7 @@ func (e *EdgeTunnelServer) serveQUIC(
// The reportErrorToSentry is an helper function that handles
// verifies if an error should be reported to Sentry.
func (e *EdgeTunnelServer) reportErrorToSentry(err error) {
func (e *EdgeTunnelServer) reportErrorToSentry(err error, pqMode features.PostQuantumMode) {
dialErr, ok := err.(*connection.EdgeQuicDialError)
if ok {
// The TransportError provides an Unwrap function however
@@ -681,7 +675,7 @@ func (e *EdgeTunnelServer) reportErrorToSentry(err error) {
if ok &&
transportErr.ErrorCode.IsCryptoError() &&
fips.IsFipsEnabled() &&
e.config.FeatureSelector.PostQuantumMode() == features.PostQuantumStrict {
pqMode == features.PostQuantumStrict {
// Only report to Sentry when using FIPS, PQ,
// and the error is a Crypto error reported by
// an EdgeQuicDialError