TUN-10390: Call prechecks

Final run method, which runs cloudlflared pre-checks for both the normal startup procedure, as well as cloudflared diag.

For cloudflared diag, this produces a new json output to which is added to the final zip file.

Also added in a new flag to prevent this from running all the time, at least for now until we are 100% sure this works as intended. We will later remove this flag, only leaving in `--no-prechecks`, so this runs by default for everyone using cloudflared.

Tested pre-checks locally with origintunneld. The results show all pre-checks succeeding. In this case, it ran with only 1 region, since locally we run it with `--edge origintunneld1:7844`.

![Screenshot 2026-05-07 at 13.19.19.png](/uploads/8d0031d7c819d8a761707fe9d845667f/Screenshot_2026-05-07_at_13.19.19.png){width=900 height=217}
This commit is contained in:
Miguel da Costa Martins Marcelino
2026-05-07 17:27:58 +00:00
parent 22a955f7bb
commit a67c583bf1
7 changed files with 126 additions and 2 deletions
+3
View File
@@ -126,6 +126,9 @@ const (
// NoPrechecks is the command line flag to skip connectivity pre-checks at startup. // NoPrechecks is the command line flag to skip connectivity pre-checks at startup.
NoPrechecks = "no-prechecks" 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 is the command line flag for the cloudflared logging level
LogLevel = "loglevel" LogLevel = "loglevel"
+64
View File
@@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"context" "context"
"fmt" "fmt"
"net"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
@@ -31,11 +32,13 @@ import (
"github.com/cloudflare/cloudflared/credentials" "github.com/cloudflare/cloudflared/credentials"
"github.com/cloudflare/cloudflared/diagnostic" "github.com/cloudflare/cloudflared/diagnostic"
"github.com/cloudflare/cloudflared/edgediscovery" "github.com/cloudflare/cloudflared/edgediscovery"
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
"github.com/cloudflare/cloudflared/ingress" "github.com/cloudflare/cloudflared/ingress"
"github.com/cloudflare/cloudflared/logger" "github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/management" "github.com/cloudflare/cloudflared/management"
"github.com/cloudflare/cloudflared/metrics" "github.com/cloudflare/cloudflared/metrics"
"github.com/cloudflare/cloudflared/orchestration" "github.com/cloudflare/cloudflared/orchestration"
"github.com/cloudflare/cloudflared/prechecks"
"github.com/cloudflare/cloudflared/signal" "github.com/cloudflare/cloudflared/signal"
"github.com/cloudflare/cloudflared/supervisor" "github.com/cloudflare/cloudflared/supervisor"
"github.com/cloudflare/cloudflared/tlsconfig" "github.com/cloudflare/cloudflared/tlsconfig"
@@ -372,6 +375,17 @@ func StartServer(
info.Log(log) info.Log(log)
logClientOptions(c, 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 // 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) ctx, cancel := context.WithCancel(c.Context)
defer cancel() defer cancel()
@@ -513,6 +527,49 @@ func StartServer(
return waitToShutdown(&wg, cancel, errC, graceShutdownC, gracePeriod, log) return waitToShutdown(&wg, cancel, errC, graceShutdownC, gracePeriod, log)
} }
// runPrechecks executes connectivity pre-checks and logs the results.
// Pre-checks are diagnostic only and do not gate tunnel startup.
func runPrechecks(c *cli.Context, log *zerolog.Logger, region string) {
ipVersion := allregions.Auto
if ipVersionStr := c.String(cfdflags.EdgeIpVersion); ipVersionStr != "" {
parsedVersion, err := parseConfigIPVersion(ipVersionStr)
if err == nil {
ipVersion = parsedVersion
} else {
log.Warn().Str("edgeIpVersion", ipVersionStr).Err(err).Msg("Invalid edge-ip-version value, using auto")
}
}
cfg := prechecks.Config{
Region: region,
IPVersion: ipVersion,
}
// Mirror the static/dynamic edge selection from supervisor/supervisor.go:
// when --edge addresses are provided, bypass DNS discovery entirely.
var dnsResolver prechecks.DNSResolver
if edgeAddrs := c.StringSlice(cfdflags.Edge); len(edgeAddrs) > 0 {
dnsResolver = &prechecks.StaticEdgeDNSResolver{Addrs: edgeAddrs, Log: log}
} else {
dnsResolver = &prechecks.EdgeDNSResolver{Log: log}
}
dialers := prechecks.RunDialers{
DNSResolver: dnsResolver,
TCPDialer: &prechecks.EdgeTCPDialer{},
QUICDialer: &prechecks.EdgeQUICDialer{},
ManagementDialer: &prechecks.NetManagementDialer{Dialer: net.Dialer{}},
}
report := prechecks.Run(c.Context, c.String(cfdflags.CACert), cfg, log, dialers)
// Output the human-readable table to console
fmt.Println(report.String())
// Also log structured results for log aggregation
report.LogEvent(log)
}
func waitToShutdown(wg *sync.WaitGroup, func waitToShutdown(wg *sync.WaitGroup,
cancelServerContext func(), cancelServerContext func(),
errC <-chan error, errC <-chan error,
@@ -889,6 +946,13 @@ func configureCloudflaredFlags(shouldHide bool) []cli.Flag {
Value: false, Value: false,
Hidden: shouldHide, 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{ altsrc.NewStringFlag(&cli.StringFlag{
Name: cfdflags.Metrics, Name: cfdflags.Metrics,
Value: metrics.GetMetricsDefaultAddress(metrics.Runtime), Value: metrics.GetMetricsDefaultAddress(metrics.Runtime),
+1
View File
@@ -262,6 +262,7 @@ func prepareTunnelConfig(
QUICConnectionLevelFlowControlLimit: c.Uint64(flags.QuicConnLevelFlowControlLimit), QUICConnectionLevelFlowControlLimit: c.Uint64(flags.QuicConnLevelFlowControlLimit),
QUICStreamLevelFlowControlLimit: c.Uint64(flags.QuicStreamLevelFlowControlLimit), QUICStreamLevelFlowControlLimit: c.Uint64(flags.QuicStreamLevelFlowControlLimit),
NoPrechecks: c.Bool(flags.NoPrechecks), NoPrechecks: c.Bool(flags.NoPrechecks),
Prechecks: c.Bool(flags.Prechecks),
OriginDNSService: dnsService, OriginDNSService: dnsService,
OriginDialerService: originDialerService, OriginDialerService: originDialerService,
} }
+2 -1
View File
@@ -489,7 +489,7 @@ func readyCommand(c *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
// nolint: gosec // nolint: gosec // URL is constructed from the user-configured local metrics endpoint.
res, err := http.DefaultClient.Do(req) res, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return err return err
@@ -1109,6 +1109,7 @@ func diagCommand(ctx *cli.Context) error {
Address: sctx.c.String(flags.Metrics), Address: sctx.c.String(flags.Metrics),
ContainerID: sctx.c.String(diagContainerIDFlagName), ContainerID: sctx.c.String(diagContainerIDFlagName),
PodID: sctx.c.String(diagPodFlagName), PodID: sctx.c.String(diagPodFlagName),
Region: sctx.c.String(flags.Region),
Toggles: diagnostic.Toggles{ Toggles: diagnostic.Toggles{
NoDiagLogs: sctx.c.Bool(noDiagLogsFlagName), NoDiagLogs: sctx.c.Bool(noDiagLogsFlagName),
NoDiagMetrics: sctx.c.Bool(noDiagMetricsFlagName), NoDiagMetrics: sctx.c.Bool(noDiagMetricsFlagName),
+1
View File
@@ -34,4 +34,5 @@ const (
cliConfigurationBaseName = "cli-configuration.json" cliConfigurationBaseName = "cli-configuration.json"
configurationBaseName = "configuration.json" configurationBaseName = "configuration.json"
taskResultBaseName = "task-result.json" taskResultBaseName = "task-result.json"
prechecksBaseName = "prechecks.json"
) )
+52 -1
View File
@@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
@@ -16,6 +17,8 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
network "github.com/cloudflare/cloudflared/diagnostic/network" network "github.com/cloudflare/cloudflared/diagnostic/network"
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
"github.com/cloudflare/cloudflared/prechecks"
) )
const ( const (
@@ -32,6 +35,7 @@ const (
networkInformationJobName = "network information" networkInformationJobName = "network information"
cliConfigurationJobName = "cli configuration" cliConfigurationJobName = "cli configuration"
configurationJobName = "configuration" configurationJobName = "configuration"
prechecksJobName = "connectivity pre-checks"
) )
// Struct used to hold the results of different routines executing the network collection. // Struct used to hold the results of different routines executing the network collection.
@@ -92,6 +96,7 @@ type Options struct {
Address string Address string
ContainerID string ContainerID string
PodID string PodID string
Region string
Toggles Toggles Toggles Toggles
} }
@@ -230,7 +235,7 @@ func networkInformationCollectors() (rawNetworkCollector, jsonNetworkCollector c
} }
func rawNetworkInformationWriter(resultMap map[string]networkCollectionResult) (string, error) { func rawNetworkInformationWriter(resultMap map[string]networkCollectionResult) (string, error) {
// nolint: gosec // nolint: gosec // Intentionally creating a temporary diagnostic file in the OS temp directory.
networkDumpHandle, err := os.Create(filepath.Join(os.TempDir(), rawNetworkBaseName)) networkDumpHandle, err := os.Create(filepath.Join(os.TempDir(), rawNetworkBaseName))
if err != nil { if err != nil {
return "", ErrCreatingTemporaryFile return "", ErrCreatingTemporaryFile
@@ -372,6 +377,7 @@ func resolveInstanceBaseURL(
func createJobs( func createJobs(
client *httpClient, client *httpClient,
tunnel *TunnelState, tunnel *TunnelState,
region string,
diagContainer string, diagContainer string,
diagPod string, diagPod string,
noDiagSystem bool, noDiagSystem bool,
@@ -434,11 +440,55 @@ func createJobs(
fn: collectFromEndpointAdapter(client.GetTunnelConfiguration, configurationBaseName), fn: collectFromEndpointAdapter(client.GetTunnelConfiguration, configurationBaseName),
bypass: false, bypass: false,
}, },
{
jobName: prechecksJobName,
fn: collectPrechecks(region),
bypass: noDiagNetwork,
},
} }
return jobs return jobs
} }
// collectPrechecks runs connectivity pre-checks and writes the results to a JSON file.
func collectPrechecks(region string) collectFunc {
return func(ctx context.Context) (string, error) {
cfg := prechecks.Config{
Region: region,
IPVersion: allregions.Auto,
Timeout: defaultTimeout,
}
// Create a no-op logger since we don't want to spam logs during diagnostic collection
log := zerolog.New(io.Discard)
dialers := prechecks.RunDialers{
DNSResolver: &prechecks.EdgeDNSResolver{Log: &log},
TCPDialer: &prechecks.EdgeTCPDialer{},
QUICDialer: &prechecks.EdgeQUICDialer{},
ManagementDialer: &prechecks.NetManagementDialer{Dialer: net.Dialer{}},
}
emptyCert := ""
report := prechecks.Run(ctx, emptyCert, cfg, &log, dialers)
// Write the report to a JSON file
// nolint: gosec
dumpHandle, err := os.Create(filepath.Join(os.TempDir(), prechecksBaseName))
if err != nil {
return "", ErrCreatingTemporaryFile
}
defer func() { _ = dumpHandle.Close() }()
encoder := newFormattedEncoder(dumpHandle)
if err := encoder.Encode(report); err != nil {
return dumpHandle.Name(), fmt.Errorf("error encoding prechecks report: %w", err)
}
return dumpHandle.Name(), nil
}
}
func createTaskReport(taskReport map[string]taskResult) (string, error) { func createTaskReport(taskReport map[string]taskResult) (string, error) {
// nolint: gosec // nolint: gosec
dumpHandle, err := os.Create(filepath.Join(os.TempDir(), taskResultBaseName)) dumpHandle, err := os.Create(filepath.Join(os.TempDir(), taskResultBaseName))
@@ -527,6 +577,7 @@ func RunDiagnostic(
jobs := createJobs( jobs := createJobs(
client, client,
tunnel, tunnel,
options.Region,
options.ContainerID, options.ContainerID,
options.PodID, options.PodID,
options.Toggles.NoDiagSystem, options.Toggles.NoDiagSystem,
+3
View File
@@ -65,6 +65,9 @@ type TunnelConfig struct {
// NoPrechecks disables connectivity pre-checks at startup. // NoPrechecks disables connectivity pre-checks at startup.
NoPrechecks bool NoPrechecks bool
// Prechecks enables connectivity pre-checks at startup.
Prechecks bool
NamedTunnel *connection.TunnelProperties NamedTunnel *connection.TunnelProperties
ProtocolSelector connection.ProtocolSelector ProtocolSelector connection.ProtocolSelector
EdgeTLSConfigs map[connection.Protocol]*tls.Config EdgeTLSConfigs map[connection.Protocol]*tls.Config