From 31de04f8582a5541a6f79bf74f452bd79dca2371 Mon Sep 17 00:00:00 2001 From: Miguel da Costa Martins Marcelino Date: Wed, 13 May 2026 18:05:11 +0000 Subject: [PATCH] TUN-10525: Add prechecks kill switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of having theĀ  --precheck flag in cloudflared, we allow controlling prechecks via a DNS flag, so we can short-circuit this behavior in case anything goes wrong. Although we don't expect pre-checks to add that much traffic, we should still guarantee that we can stop pre-checks in case something goes wrong. --- cmd/cloudflared/flags/flags.go | 3 --- cmd/cloudflared/tunnel/cmd.go | 25 +++++++------------------ cmd/cloudflared/tunnel/configuration.go | 1 - features/features.go | 4 ++++ features/selector.go | 10 +++++++++- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/cmd/cloudflared/flags/flags.go b/cmd/cloudflared/flags/flags.go index 89eadd8a..f31ff60e 100644 --- a/cmd/cloudflared/flags/flags.go +++ b/cmd/cloudflared/flags/flags.go @@ -126,9 +126,6 @@ const ( // NoPrechecks is the command line flag to skip connectivity pre-checks at startup. NoPrechecks = "no-prechecks" - // Prechecks is the command line flag to run connectivity pre-checks at startup. - Prechecks = "prechecks" - // LogLevel is the command line flag for the cloudflared logging level LogLevel = "loglevel" diff --git a/cmd/cloudflared/tunnel/cmd.go b/cmd/cloudflared/tunnel/cmd.go index f37e76fd..9a33fb49 100644 --- a/cmd/cloudflared/tunnel/cmd.go +++ b/cmd/cloudflared/tunnel/cmd.go @@ -375,17 +375,6 @@ func StartServer( info.Log(log) logClientOptions(c, log) - // Run connectivity pre-checks for cloudflared. This runs in a separate - // goroutine, as we want to keep initializing cloudflared while prechecks - // are running. - if c.Bool(cfdflags.Prechecks) && !c.Bool(cfdflags.NoPrechecks) { - resolvedRegion := c.String(cfdflags.Region) - if resolvedRegion == "" && namedTunnel != nil { - resolvedRegion = namedTunnel.Credentials.Endpoint - } - go runPrechecks(c, log, resolvedRegion) - } - // this context drives the server, when it's canceled tunnel and all other components (origins, dns, etc...) should stop ctx, cancel := context.WithCancel(c.Context) defer cancel() @@ -428,6 +417,13 @@ func StartServer( } connectorID := tunnelConfig.ClientConfig.ConnectorID + // Run connectivity pre-checks for cloudflared. This runs in a separate + // goroutine, as we want to keep initializing cloudflared while prechecks + // are running. Prechecks are controlled via DNS flag for remote kill-switch capability. + if !tunnelConfig.ClientConfig.ConnectionFeaturesSnapshot().SkipPrechecks && !c.Bool(cfdflags.NoPrechecks) { + go runPrechecks(c, log, tunnelConfig.Region) + } + // Disable ICMP packet routing for quick tunnels if quickTunnelURL != "" { tunnelConfig.ICMPRouterServer = nil @@ -946,13 +942,6 @@ func configureCloudflaredFlags(shouldHide bool) []cli.Flag { Value: false, Hidden: shouldHide, }), - altsrc.NewBoolFlag(&cli.BoolFlag{ - Name: cfdflags.Prechecks, - Usage: "Run connectivity pre-checks at startup.", - EnvVars: []string{"TUNNEL_PRECHECKS"}, - Value: false, - Hidden: shouldHide, - }), altsrc.NewStringFlag(&cli.StringFlag{ Name: cfdflags.Metrics, Value: metrics.GetMetricsDefaultAddress(metrics.Runtime), diff --git a/cmd/cloudflared/tunnel/configuration.go b/cmd/cloudflared/tunnel/configuration.go index d9d7ce05..f4f9a674 100644 --- a/cmd/cloudflared/tunnel/configuration.go +++ b/cmd/cloudflared/tunnel/configuration.go @@ -252,7 +252,6 @@ func prepareTunnelConfig( QUICConnectionLevelFlowControlLimit: c.Uint64(flags.QuicConnLevelFlowControlLimit), QUICStreamLevelFlowControlLimit: c.Uint64(flags.QuicStreamLevelFlowControlLimit), NoPrechecks: c.Bool(flags.NoPrechecks), - Prechecks: c.Bool(flags.Prechecks), OriginDNSService: dnsService, OriginDialerService: originDialerService, } diff --git a/features/features.go b/features/features.go index 83a34a7f..dc0c6b5f 100644 --- a/features/features.go +++ b/features/features.go @@ -42,6 +42,10 @@ type FeatureSnapshot struct { // We provide the list of features since we need it to send in the ConnectionOptions during connection // registrations. FeaturesList []string + + // SkipPrechecks indicates when to skip connectivity pre-checks at startup. + // Controlled via DNS TXT record to allow remote kill-switch in case of issues. + SkipPrechecks bool } type PostQuantumMode uint8 diff --git a/features/selector.go b/features/selector.go index d76276e8..34c14341 100644 --- a/features/selector.go +++ b/features/selector.go @@ -24,6 +24,7 @@ const ( type featuresRecord struct { DatagramV3Percentage uint32 `json:"dv3_2"` + SkipPrechecks bool `json:"skip_prechecks"` // DatagramV3Percentage int32 `json:"dv3"` // Removed in TUN-9291 // DatagramV3Percentage uint32 `json:"dv3_1"` // Removed in TUN-9883 @@ -89,6 +90,7 @@ func (fs *featureSelector) Snapshot() FeatureSnapshot { PostQuantum: fs.postQuantumMode(), DatagramVersion: fs.datagramVersion(), FeaturesList: fs.clientFeatures(), + SkipPrechecks: fs.prechecksSkip(), } } @@ -121,6 +123,12 @@ func (fs *featureSelector) datagramVersion() DatagramVersion { return DatagramV2 } +// prechecksSkip returns whether prechecks are enabled via DNS flag. +// Defaults to false if not set in the DNS TXT record. +func (fs *featureSelector) prechecksSkip() bool { + return fs.remoteFeatures.SkipPrechecks +} + // clientFeatures will return the list of currently available features that cloudflared should provide to the edge. func (fs *featureSelector) clientFeatures() []string { // Evaluate any remote features along with static feature list to construct the list of features @@ -186,7 +194,7 @@ func (dr *dnsResolver) lookupRecord(ctx context.Context) ([]byte, error) { } if len(records) == 0 { - return nil, fmt.Errorf("No TXT record found for %s to determine which features to opt-in", featureSelectorHostname) + return nil, fmt.Errorf("no TXT record found for %s to determine which features to opt-in", featureSelectorHostname) } return []byte(records[0]), nil