mirror of
https://github.com/cloudflare/cloudflared
synced 2026-06-08 13:33:07 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f8d12c9d39 | |||
| 96ce66bd30 | |||
| e144eac2af | |||
| a62d63d49d | |||
| 3bf9217de5 | |||
| 02705c44b2 | |||
| ce27840573 |
+1
-2
@@ -57,8 +57,7 @@ build_cloudflared_macos: &build
|
||||
- '[ "${RUNNER_ARCH}" = "intel" ] && export TARGET_ARCH=amd64'
|
||||
- ARCH=$(uname -m)
|
||||
- echo ARCH=$ARCH - TARGET_ARCH=$TARGET_ARCH
|
||||
- ./.teamcity/mac/install-cloudflare-go.sh
|
||||
- export PATH="/tmp/go/bin:$PATH"
|
||||
- ./.teamcity/mac/install-go.sh
|
||||
- BUILD_SCRIPT=.teamcity/mac/build.sh
|
||||
- if [[ ! -x ${BUILD_SCRIPT} ]] ; then exit ; fi
|
||||
- set -euo pipefail
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ linters:
|
||||
- sloglint # Ensure consistent code style when using log/slog.
|
||||
- sqlclosecheck # Checks that sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query are closed.
|
||||
- staticcheck # It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary.
|
||||
- tenv # Tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17.
|
||||
- usetesting # Reports uses of functions with replacement inside the testing package.
|
||||
- testableexamples # Linter checks if examples are testable (have an expected output).
|
||||
- testifylint # Checks usage of github.com/stretchr/testify.
|
||||
- tparallel # Tparallel detects inappropriate usage of t.Parallel() method in your Go test codes.
|
||||
|
||||
+1
-1
@@ -7,4 +7,4 @@ rm -rf $GOCACHE
|
||||
export PATH="/tmp/go/bin:$PATH"
|
||||
go version
|
||||
which go
|
||||
go env
|
||||
go env
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
rm -rf /tmp/go
|
||||
export GOCACHE=/tmp/gocache
|
||||
rm -rf $GOCACHE
|
||||
|
||||
brew install go@1.24
|
||||
|
||||
go version
|
||||
which go
|
||||
go env
|
||||
|
||||
Vendored
+2
-2
@@ -32,7 +32,7 @@ Write-Output "Running unit tests"
|
||||
|
||||
# Not testing with race detector because of https://github.com/golang/go/issues/61058
|
||||
# We already test it on other platforms
|
||||
& go test -failfast -mod=vendor ./...
|
||||
go test -failfast -v -mod=vendor ./...
|
||||
if ($LASTEXITCODE -ne 0) { throw "Failed unit tests" }
|
||||
|
||||
Write-Output "Running component tests"
|
||||
@@ -44,4 +44,4 @@ if ($LASTEXITCODE -ne 0) {
|
||||
python component-tests/setup.py --type cleanup
|
||||
throw "Failed component tests"
|
||||
}
|
||||
python component-tests/setup.py --type cleanup
|
||||
python component-tests/setup.py --type cleanup
|
||||
|
||||
+2
-4
@@ -1,7 +1,7 @@
|
||||
# use a builder image for building cloudflare
|
||||
ARG TARGET_GOOS
|
||||
ARG TARGET_GOARCH
|
||||
FROM golang:1.22.10 as builder
|
||||
FROM golang:1.24.2 AS builder
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0 \
|
||||
TARGET_GOOS=${TARGET_GOOS} \
|
||||
@@ -16,10 +16,8 @@ WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
# copy our sources into the builder image
|
||||
COPY . .
|
||||
|
||||
RUN .teamcity/install-cloudflare-go.sh
|
||||
|
||||
# compile cloudflared
|
||||
RUN PATH="/tmp/go/bin:$PATH" make cloudflared
|
||||
RUN make cloudflared
|
||||
|
||||
# use a distroless base image with glibc
|
||||
FROM gcr.io/distroless/base-debian12:nonroot
|
||||
|
||||
+2
-4
@@ -1,5 +1,5 @@
|
||||
# use a builder image for building cloudflare
|
||||
FROM golang:1.22.10 as builder
|
||||
FROM golang:1.24.2 AS builder
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0 \
|
||||
# the CONTAINER_BUILD envvar is used set github.com/cloudflare/cloudflared/metrics.Runtime=virtual
|
||||
@@ -11,10 +11,8 @@ WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
# copy our sources into the builder image
|
||||
COPY . .
|
||||
|
||||
RUN .teamcity/install-cloudflare-go.sh
|
||||
|
||||
# compile cloudflared
|
||||
RUN GOOS=linux GOARCH=amd64 PATH="/tmp/go/bin:$PATH" make cloudflared
|
||||
RUN GOOS=linux GOARCH=amd64 make cloudflared
|
||||
|
||||
# use a distroless base image with glibc
|
||||
FROM gcr.io/distroless/base-debian12:nonroot
|
||||
|
||||
+2
-4
@@ -1,5 +1,5 @@
|
||||
# use a builder image for building cloudflare
|
||||
FROM golang:1.22.10 as builder
|
||||
FROM golang:1.24.2 AS builder
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0 \
|
||||
# the CONTAINER_BUILD envvar is used set github.com/cloudflare/cloudflared/metrics.Runtime=virtual
|
||||
@@ -11,10 +11,8 @@ WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
# copy our sources into the builder image
|
||||
COPY . .
|
||||
|
||||
RUN .teamcity/install-cloudflare-go.sh
|
||||
|
||||
# compile cloudflared
|
||||
RUN GOOS=linux GOARCH=arm64 PATH="/tmp/go/bin:$PATH" make cloudflared
|
||||
RUN GOOS=linux GOARCH=arm64 make cloudflared
|
||||
|
||||
# use a distroless base image with glibc
|
||||
FROM gcr.io/distroless/base-debian12:nonroot-arm64
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
2025.6.0
|
||||
- 2025-06-06 TUN-9016: update go to 1.24
|
||||
- 2025-06-05 TUN-9171: Use `is_default_network` instead of `is_default` to create vnet's
|
||||
|
||||
2025.5.0
|
||||
- 2025-05-14 TUN-9319: Add dynamic loading of features to connections via ConnectionOptionsSnapshot
|
||||
- 2025-05-13 TUN-9322: Add metric for unsupported RPC commands for datagram v3
|
||||
- 2025-05-07 TUN-9291: Remove dynamic reloading of features for datagram v3
|
||||
|
||||
2025.4.2
|
||||
- 2025-04-30 chore: Do not use gitlab merge request pipelines
|
||||
- 2025-04-30 DEVTOOLS-16383: Create GitlabCI pipeline to release Mac builds
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
type NewVirtualNetwork struct {
|
||||
Name string `json:"name"`
|
||||
Comment string `json:"comment"`
|
||||
IsDefault bool `json:"is_default"`
|
||||
IsDefault bool `json:"is_default_network"`
|
||||
}
|
||||
|
||||
type VirtualNetwork struct {
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
pinned_go: &pinned_go go-boring=1.22.10-1
|
||||
pinned_go: &pinned_go go-boring=1.24.2-1
|
||||
|
||||
build_dir: &build_dir /cfsetup_build
|
||||
default-flavor: bookworm
|
||||
@@ -13,7 +13,7 @@ bullseye: &bullseye
|
||||
- rubygem-fpm
|
||||
- rpm
|
||||
- libffi-dev
|
||||
- golangci-lint
|
||||
- golangci-lint=1.64.8-2
|
||||
pre-cache: &build_pre_cache
|
||||
- export GOCACHE=/cfsetup_build/.cache/go-build
|
||||
- go install golang.org/x/tools/cmd/goimports@v0.30.0
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/features"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// Config captures the local client runtime configuration.
|
||||
type Config struct {
|
||||
ConnectorID uuid.UUID
|
||||
Version string
|
||||
Arch string
|
||||
|
||||
featureSelector features.FeatureSelector
|
||||
}
|
||||
|
||||
func NewConfig(version string, arch string, featureSelector features.FeatureSelector) (*Config, error) {
|
||||
connectorID, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to generate a connector UUID: %w", err)
|
||||
}
|
||||
return &Config{
|
||||
ConnectorID: connectorID,
|
||||
Version: version,
|
||||
Arch: arch,
|
||||
featureSelector: featureSelector,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ConnectionOptionsSnapshot is a snapshot of the current client information used to initialize a connection.
|
||||
//
|
||||
// The FeatureSnapshot is the features that are available for this connection. At the client level they may
|
||||
// change, but they will not change within the scope of this struct.
|
||||
type ConnectionOptionsSnapshot struct {
|
||||
client pogs.ClientInfo
|
||||
originLocalIP net.IP
|
||||
numPreviousAttempts uint8
|
||||
FeatureSnapshot features.FeatureSnapshot
|
||||
}
|
||||
|
||||
func (c *Config) ConnectionOptionsSnapshot(originIP net.IP, previousAttempts uint8) *ConnectionOptionsSnapshot {
|
||||
snapshot := c.featureSelector.Snapshot()
|
||||
return &ConnectionOptionsSnapshot{
|
||||
client: pogs.ClientInfo{
|
||||
ClientID: c.ConnectorID[:],
|
||||
Version: c.Version,
|
||||
Arch: c.Arch,
|
||||
Features: snapshot.FeaturesList,
|
||||
},
|
||||
originLocalIP: originIP,
|
||||
numPreviousAttempts: previousAttempts,
|
||||
FeatureSnapshot: snapshot,
|
||||
}
|
||||
}
|
||||
|
||||
func (c ConnectionOptionsSnapshot) ConnectionOptions() *pogs.ConnectionOptions {
|
||||
return &pogs.ConnectionOptions{
|
||||
Client: c.client,
|
||||
OriginLocalIP: c.originLocalIP,
|
||||
ReplaceExisting: false,
|
||||
CompressionQuality: 0,
|
||||
NumPreviousAttempts: c.numPreviousAttempts,
|
||||
}
|
||||
}
|
||||
|
||||
func (c ConnectionOptionsSnapshot) LogFields(event *zerolog.Event) *zerolog.Event {
|
||||
return event.Strs("features", c.client.Features)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cloudflare/cloudflared/features"
|
||||
)
|
||||
|
||||
func TestGenerateConnectionOptions(t *testing.T) {
|
||||
version := "1234"
|
||||
arch := "linux_amd64"
|
||||
originIP := net.ParseIP("192.168.1.1")
|
||||
var previousAttempts uint8 = 4
|
||||
|
||||
config, err := NewConfig(version, arch, &mockFeatureSelector{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, version, config.Version)
|
||||
require.Equal(t, arch, config.Arch)
|
||||
|
||||
// Validate ConnectionOptionsSnapshot fields
|
||||
connOptions := config.ConnectionOptionsSnapshot(originIP, previousAttempts)
|
||||
require.Equal(t, version, connOptions.client.Version)
|
||||
require.Equal(t, arch, connOptions.client.Arch)
|
||||
require.Equal(t, config.ConnectorID[:], connOptions.client.ClientID)
|
||||
|
||||
// Vaidate snapshot feature fields against the connOptions generated
|
||||
snapshot := config.featureSelector.Snapshot()
|
||||
require.Equal(t, features.DatagramV3, snapshot.DatagramVersion)
|
||||
require.Equal(t, features.DatagramV3, connOptions.FeatureSnapshot.DatagramVersion)
|
||||
|
||||
pogsConnOptions := connOptions.ConnectionOptions()
|
||||
require.Equal(t, connOptions.client, pogsConnOptions.Client)
|
||||
require.Equal(t, originIP, pogsConnOptions.OriginLocalIP)
|
||||
require.False(t, pogsConnOptions.ReplaceExisting)
|
||||
require.Equal(t, uint8(0), pogsConnOptions.CompressionQuality)
|
||||
require.Equal(t, previousAttempts, pogsConnOptions.NumPreviousAttempts)
|
||||
}
|
||||
|
||||
type mockFeatureSelector struct{}
|
||||
|
||||
func (m *mockFeatureSelector) Snapshot() features.FeatureSnapshot {
|
||||
return features.FeatureSnapshot{
|
||||
PostQuantum: features.PostQuantumPrefer,
|
||||
DatagramVersion: features.DatagramV3,
|
||||
FeaturesList: []string{features.FeaturePostQuantum, features.FeatureDatagramV3_1},
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
)
|
||||
|
||||
func runApp(app *cli.App, graceShutdownC chan struct{}) {
|
||||
func runApp(app *cli.App, _ chan struct{}) {
|
||||
app.Commands = append(app.Commands, &cli.Command{
|
||||
Name: "service",
|
||||
Usage: "Manages the cloudflared system service",
|
||||
@@ -35,7 +36,7 @@ func runApp(app *cli.App, graceShutdownC chan struct{}) {
|
||||
},
|
||||
},
|
||||
})
|
||||
app.Run(os.Args)
|
||||
_ = app.Run(os.Args)
|
||||
}
|
||||
|
||||
// The directory and files that are used by the service.
|
||||
@@ -97,6 +98,7 @@ WantedBy=timers.target
|
||||
var sysvTemplate = ServiceTemplate{
|
||||
Path: "/etc/init.d/cloudflared",
|
||||
FileMode: 0755,
|
||||
// nolint: dupword
|
||||
Content: `#!/bin/sh
|
||||
# For RedHat and cousins:
|
||||
# chkconfig: 2345 99 01
|
||||
@@ -184,13 +186,11 @@ exit 0
|
||||
`,
|
||||
}
|
||||
|
||||
var (
|
||||
noUpdateServiceFlag = &cli.BoolFlag{
|
||||
Name: "no-update-service",
|
||||
Usage: "Disable auto-update of the cloudflared linux service, which restarts the server to upgrade for new versions.",
|
||||
Value: false,
|
||||
}
|
||||
)
|
||||
var noUpdateServiceFlag = &cli.BoolFlag{
|
||||
Name: "no-update-service",
|
||||
Usage: "Disable auto-update of the cloudflared linux service, which restarts the server to upgrade for new versions.",
|
||||
Value: false,
|
||||
}
|
||||
|
||||
func isSystemd() bool {
|
||||
if _, err := os.Stat("/run/systemd/system"); err == nil {
|
||||
@@ -430,3 +430,38 @@ func uninstallSysv(log *zerolog.Logger) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureConfigDirExists(configDir string) error {
|
||||
ok, err := config.FileExists(configDir)
|
||||
if !ok && err == nil {
|
||||
err = os.Mkdir(configDir, 0755)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func copyFile(src, dest string) error {
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
destFile, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ok := false
|
||||
defer func() {
|
||||
destFile.Close()
|
||||
if !ok {
|
||||
_ = os.Remove(dest)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := io.Copy(destFile, srcFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"text/template"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
)
|
||||
|
||||
type ServiceTemplate struct {
|
||||
@@ -109,38 +107,3 @@ func runCommand(command string, args ...string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureConfigDirExists(configDir string) error {
|
||||
ok, err := config.FileExists(configDir)
|
||||
if !ok && err == nil {
|
||||
err = os.Mkdir(configDir, 0755)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func copyFile(src, dest string) error {
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
destFile, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ok := false
|
||||
defer func() {
|
||||
destFile.Close()
|
||||
if !ok {
|
||||
_ = os.Remove(dest)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := io.Copy(destFile, srcFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/coreos/go-systemd/v22/daemon"
|
||||
"github.com/facebookgo/grace/gracenet"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/google/uuid"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
@@ -446,14 +445,7 @@ func StartServer(
|
||||
log.Err(err).Msg("Couldn't start tunnel")
|
||||
return err
|
||||
}
|
||||
var clientID uuid.UUID
|
||||
if tunnelConfig.NamedTunnel != nil {
|
||||
clientID, err = uuid.FromBytes(tunnelConfig.NamedTunnel.Client.ClientID)
|
||||
if err != nil {
|
||||
// set to nil for classic tunnels
|
||||
clientID = uuid.Nil
|
||||
}
|
||||
}
|
||||
connectorID := tunnelConfig.ClientConfig.ConnectorID
|
||||
|
||||
// Disable ICMP packet routing for quick tunnels
|
||||
if quickTunnelURL != "" {
|
||||
@@ -471,7 +463,7 @@ func StartServer(
|
||||
c.String("management-hostname"),
|
||||
c.Bool("management-diagnostics"),
|
||||
serviceIP,
|
||||
clientID,
|
||||
connectorID,
|
||||
c.String(cfdflags.ConnectorLabel),
|
||||
logger.ManagementLogger.Log,
|
||||
logger.ManagementLogger,
|
||||
@@ -503,14 +495,14 @@ func StartServer(
|
||||
sources = append(sources, ipv6.String())
|
||||
}
|
||||
|
||||
readinessServer := metrics.NewReadyServer(clientID, tracker)
|
||||
readinessServer := metrics.NewReadyServer(connectorID, tracker)
|
||||
cliFlags := nonSecretCliFlags(log, c, nonSecretFlagsList)
|
||||
diagnosticHandler := diagnostic.NewDiagnosticHandler(
|
||||
log,
|
||||
0,
|
||||
diagnostic.NewSystemCollectorImpl(buildInfo.CloudflaredVersion),
|
||||
tunnelConfig.NamedTunnel.Credentials.TunnelID,
|
||||
clientID,
|
||||
connectorID,
|
||||
tracker,
|
||||
cliFlags,
|
||||
sources,
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cloudflare/cloudflared/features"
|
||||
)
|
||||
|
||||
func TestDedup(t *testing.T) {
|
||||
expected := []string{"a", "b"}
|
||||
actual := features.Dedup([]string{"a", "b", "a"})
|
||||
require.ElementsMatch(t, expected, actual)
|
||||
}
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/cloudflare/cloudflared/client"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/flags"
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
@@ -125,27 +125,29 @@ func prepareTunnelConfig(
|
||||
observer *connection.Observer,
|
||||
namedTunnel *connection.TunnelProperties,
|
||||
) (*supervisor.TunnelConfig, *orchestration.Config, error) {
|
||||
clientID, err := uuid.NewRandom()
|
||||
transportProtocol := c.String(flags.Protocol)
|
||||
isPostQuantumEnforced := c.Bool(flags.PostQuantum)
|
||||
featureSelector, err := features.NewFeatureSelector(ctx, namedTunnel.Credentials.AccountTag, c.StringSlice(flags.Features), isPostQuantumEnforced, log)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "can't generate connector UUID")
|
||||
return nil, nil, errors.Wrap(err, "Failed to create feature selector")
|
||||
}
|
||||
log.Info().Msgf("Generated Connector ID: %s", clientID)
|
||||
|
||||
clientConfig, err := client.NewConfig(info.Version(), info.OSArch(), featureSelector)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
log.Info().Msgf("Generated Connector ID: %s", clientConfig.ConnectorID)
|
||||
|
||||
tags, err := NewTagSliceFromCLI(c.StringSlice(flags.Tag))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Tag parse failure")
|
||||
return nil, nil, errors.Wrap(err, "Tag parse failure")
|
||||
}
|
||||
tags = append(tags, pogs.Tag{Name: "ID", Value: clientID.String()})
|
||||
tags = append(tags, pogs.Tag{Name: "ID", Value: clientConfig.ConnectorID.String()})
|
||||
|
||||
transportProtocol := c.String(flags.Protocol)
|
||||
isPostQuantumEnforced := c.Bool(flags.PostQuantum)
|
||||
|
||||
featureSelector, err := features.NewFeatureSelector(ctx, namedTunnel.Credentials.AccountTag, c.StringSlice("features"), c.Bool("post-quantum"), log)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "Failed to create feature selector")
|
||||
}
|
||||
clientFeatures := featureSelector.ClientFeatures()
|
||||
pqMode := featureSelector.PostQuantumMode()
|
||||
clientFeatures := featureSelector.Snapshot()
|
||||
pqMode := clientFeatures.PostQuantum
|
||||
if pqMode == features.PostQuantumStrict {
|
||||
// Error if the user tries to force a non-quic transport protocol
|
||||
if transportProtocol != connection.AutoSelectFlag && transportProtocol != connection.QUIC.String() {
|
||||
@@ -154,12 +156,6 @@ func prepareTunnelConfig(
|
||||
transportProtocol = connection.QUIC.String()
|
||||
}
|
||||
|
||||
namedTunnel.Client = pogs.ClientInfo{
|
||||
ClientID: clientID[:],
|
||||
Features: clientFeatures,
|
||||
Version: info.Version(),
|
||||
Arch: info.OSArch(),
|
||||
}
|
||||
cfg := config.GetConfiguration()
|
||||
ingressRules, err := ingress.ParseIngressFromConfigAndCLI(cfg, c, log)
|
||||
if err != nil {
|
||||
@@ -224,10 +220,8 @@ func prepareTunnelConfig(
|
||||
}
|
||||
|
||||
tunnelConfig := &supervisor.TunnelConfig{
|
||||
ClientConfig: clientConfig,
|
||||
GracePeriod: gracePeriod,
|
||||
ReplaceExisting: c.Bool(flags.Force),
|
||||
OSArch: info.OSArch(),
|
||||
ClientID: clientID.String(),
|
||||
EdgeAddrs: c.StringSlice(flags.Edge),
|
||||
Region: resolvedRegion,
|
||||
EdgeIPVersion: edgeIPVersion,
|
||||
@@ -246,7 +240,6 @@ func prepareTunnelConfig(
|
||||
NamedTunnel: namedTunnel,
|
||||
ProtocolSelector: protocolSelector,
|
||||
EdgeTLSConfigs: edgeTLSConfigs,
|
||||
FeatureSelector: featureSelector,
|
||||
MaxEdgeAddrRetries: uint8(c.Int(flags.MaxEdgeAddrRetries)), // nolint: gosec
|
||||
RPCTimeout: c.Duration(flags.RpcTimeout),
|
||||
WriteStreamTimeout: c.Duration(flags.WriteStreamTimeout),
|
||||
|
||||
@@ -761,7 +761,7 @@ func runCommand(c *cli.Context) error {
|
||||
if tokenFile := c.String(TunnelTokenFileFlag); tokenFile != "" {
|
||||
data, err := os.ReadFile(tokenFile)
|
||||
if err != nil {
|
||||
return cliutil.UsageError("Failed to read token file: " + err.Error())
|
||||
return cliutil.UsageError("Failed to read token file: %s", err.Error())
|
||||
}
|
||||
tokenStr = strings.TrimSpace(string(data))
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ func installWindowsService(c *cli.Context) error {
|
||||
log := zeroLogger.With().Str(LogFieldWindowsServiceName, windowsServiceName).Logger()
|
||||
if err == nil {
|
||||
s.Close()
|
||||
return fmt.Errorf(serviceAlreadyExistsWarn(windowsServiceName))
|
||||
return errors.New(serviceAlreadyExistsWarn(windowsServiceName))
|
||||
}
|
||||
extraArgs, err := getServiceExtraArgsFromCliArgs(c, &log)
|
||||
if err != nil {
|
||||
@@ -238,7 +238,7 @@ func uninstallWindowsService(c *cli.Context) error {
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(windowsServiceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Agent service %s is not installed, so it could not be uninstalled", windowsServiceName)
|
||||
return fmt.Errorf("agent service %s is not installed, so it could not be uninstalled", windowsServiceName)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ type Orchestrator interface {
|
||||
|
||||
type TunnelProperties struct {
|
||||
Credentials Credentials
|
||||
Client pogs.ClientInfo
|
||||
QuickTunnelUrl string
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/cloudflare/cloudflared/management"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// registerClient derives a named tunnel rpc client that can then be used to register and unregister connections.
|
||||
@@ -36,7 +36,7 @@ type controlStream struct {
|
||||
// ControlStreamHandler registers connections with origintunneld and initiates graceful shutdown.
|
||||
type ControlStreamHandler interface {
|
||||
// ServeControlStream handles the control plane of the transport in the current goroutine calling this
|
||||
ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error
|
||||
ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *pogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error
|
||||
// IsStopped tells whether the method above has finished
|
||||
IsStopped() bool
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func NewControlStream(
|
||||
func (c *controlStream) ServeControlStream(
|
||||
ctx context.Context,
|
||||
rw io.ReadWriteCloser,
|
||||
connOptions *tunnelpogs.ConnectionOptions,
|
||||
connOptions *pogs.ConnectionOptions,
|
||||
tunnelConfigGetter TunnelConfigJSONGetter,
|
||||
) error {
|
||||
registrationClient := c.registerClientFunc(ctx, rw, c.registerTimeout)
|
||||
|
||||
+4
-4
@@ -16,10 +16,10 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/client"
|
||||
cfdflow "github.com/cloudflare/cloudflared/flow"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// note: these constants are exported so we can reuse them in the edge-side code
|
||||
@@ -39,7 +39,7 @@ type HTTP2Connection struct {
|
||||
conn net.Conn
|
||||
server *http2.Server
|
||||
orchestrator Orchestrator
|
||||
connOptions *tunnelpogs.ConnectionOptions
|
||||
connOptions *client.ConnectionOptionsSnapshot
|
||||
observer *Observer
|
||||
connIndex uint8
|
||||
|
||||
@@ -54,7 +54,7 @@ type HTTP2Connection struct {
|
||||
func NewHTTP2Connection(
|
||||
conn net.Conn,
|
||||
orchestrator Orchestrator,
|
||||
connOptions *tunnelpogs.ConnectionOptions,
|
||||
connOptions *client.ConnectionOptionsSnapshot,
|
||||
observer *Observer,
|
||||
connIndex uint8,
|
||||
controlStreamHandler ControlStreamHandler,
|
||||
@@ -118,7 +118,7 @@ func (c *HTTP2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
var requestErr error
|
||||
switch connType {
|
||||
case TypeControlStream:
|
||||
requestErr = c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions, c.orchestrator)
|
||||
requestErr = c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions.ConnectionOptions(), c.orchestrator)
|
||||
if requestErr != nil {
|
||||
c.controlStreamErr = requestErr
|
||||
}
|
||||
|
||||
+14
-15
@@ -20,19 +20,18 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/client"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
var (
|
||||
testTransport = http2.Transport{}
|
||||
)
|
||||
var testTransport = http2.Transport{}
|
||||
|
||||
func newTestHTTP2Connection() (*HTTP2Connection, net.Conn) {
|
||||
edgeConn, cfdConn := net.Pipe()
|
||||
var connIndex = uint8(0)
|
||||
connIndex := uint8(0)
|
||||
log := zerolog.Nop()
|
||||
obs := NewObserver(&log, &log)
|
||||
controlStream := NewControlStream(
|
||||
@@ -51,7 +50,7 @@ func newTestHTTP2Connection() (*HTTP2Connection, net.Conn) {
|
||||
cfdConn,
|
||||
// OriginProxy is set in testConfigManager
|
||||
testOrchestrator,
|
||||
&pogs.ConnectionOptions{},
|
||||
&client.ConnectionOptionsSnapshot{},
|
||||
obs,
|
||||
connIndex,
|
||||
controlStream,
|
||||
@@ -62,7 +61,7 @@ func newTestHTTP2Connection() (*HTTP2Connection, net.Conn) {
|
||||
func TestHTTP2ConfigurationSet(t *testing.T) {
|
||||
http2Conn, edgeConn := newTestHTTP2Connection()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
@@ -74,7 +73,7 @@ func TestHTTP2ConfigurationSet(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
reqBody := []byte(`{
|
||||
"version": 2,
|
||||
"version": 2,
|
||||
"config": {"warp-routing": {"enabled": true}, "originRequest" : {"connectTimeout": 10}, "ingress" : [ {"hostname": "test", "service": "https://localhost:8000" } , {"service": "http_status:404"} ]}}
|
||||
`)
|
||||
reader := bytes.NewReader(reqBody)
|
||||
@@ -130,7 +129,7 @@ func TestServeHTTP(t *testing.T) {
|
||||
|
||||
http2Conn, edgeConn := newTestHTTP2Connection()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
@@ -261,7 +260,7 @@ func (w *wsRespWriter) close() {
|
||||
func TestServeWS(t *testing.T) {
|
||||
http2Conn, _ := newTestHTTP2Connection()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
|
||||
respWriter := newWSRespWriter()
|
||||
readPipe, writePipe := io.Pipe()
|
||||
@@ -301,7 +300,7 @@ func TestServeWS(t *testing.T) {
|
||||
func TestNoWriteAfterServeHTTPReturns(t *testing.T) {
|
||||
cfdHTTP2Conn, edgeTCPConn := newTestHTTP2Connection()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
var wg sync.WaitGroup
|
||||
|
||||
serverDone := make(chan struct{})
|
||||
@@ -379,7 +378,7 @@ func TestServeControlStream(t *testing.T) {
|
||||
)
|
||||
http2Conn.controlStreamHandler = controlStream
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
@@ -433,7 +432,7 @@ func TestFailRegistration(t *testing.T) {
|
||||
)
|
||||
http2Conn.controlStreamHandler = controlStream
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
@@ -484,7 +483,7 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
|
||||
|
||||
http2Conn.controlStreamHandler = controlStream
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
@@ -534,7 +533,7 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServeTCP_RateLimited(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
http2Conn, edgeConn := newTestHTTP2Connection()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
@@ -566,7 +565,7 @@ func TestServeTCP_RateLimited(t *testing.T) {
|
||||
func benchmarkServeHTTP(b *testing.B, test testRequest) {
|
||||
http2Conn, edgeConn := newTestHTTP2Connection()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(b.Context())
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
|
||||
@@ -17,12 +17,12 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/cloudflare/cloudflared/client"
|
||||
cfdflow "github.com/cloudflare/cloudflared/flow"
|
||||
|
||||
cfdquic "github.com/cloudflare/cloudflared/quic"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
rpcquic "github.com/cloudflare/cloudflared/tunnelrpc/quic"
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@ type quicConnection struct {
|
||||
orchestrator Orchestrator
|
||||
datagramHandler DatagramSessionHandler
|
||||
controlStreamHandler ControlStreamHandler
|
||||
connOptions *tunnelpogs.ConnectionOptions
|
||||
connOptions *client.ConnectionOptionsSnapshot
|
||||
connIndex uint8
|
||||
|
||||
rpcTimeout time.Duration
|
||||
@@ -60,7 +60,7 @@ func NewTunnelConnection(
|
||||
orchestrator Orchestrator,
|
||||
datagramSessionHandler DatagramSessionHandler,
|
||||
controlStreamHandler ControlStreamHandler,
|
||||
connOptions *pogs.ConnectionOptions,
|
||||
connOptions *client.ConnectionOptionsSnapshot,
|
||||
rpcTimeout time.Duration,
|
||||
streamWriteTimeout time.Duration,
|
||||
gracePeriod time.Duration,
|
||||
@@ -131,7 +131,7 @@ func (q *quicConnection) Serve(ctx context.Context) error {
|
||||
|
||||
// serveControlStream will serve the RPC; blocking until the control plane is done.
|
||||
func (q *quicConnection) serveControlStream(ctx context.Context, controlStream quic.Stream) error {
|
||||
return q.controlStreamHandler.ServeControlStream(ctx, controlStream, q.connOptions, q.orchestrator)
|
||||
return q.controlStreamHandler.ServeControlStream(ctx, controlStream, q.connOptions.ConnectionOptions(), q.orchestrator)
|
||||
}
|
||||
|
||||
// Close the connection with no errors specified.
|
||||
@@ -235,7 +235,7 @@ func (q *quicConnection) dispatchRequest(ctx context.Context, stream *rpcquic.Re
|
||||
}
|
||||
|
||||
// UpdateConfiguration is the RPC method invoked by edge when there is a new configuration
|
||||
func (q *quicConnection) UpdateConfiguration(ctx context.Context, version int32, config []byte) *tunnelpogs.UpdateConfigurationResponse {
|
||||
func (q *quicConnection) UpdateConfiguration(ctx context.Context, version int32, config []byte) *pogs.UpdateConfigurationResponse {
|
||||
return q.orchestrator.UpdateConfig(version, config)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/nettest"
|
||||
|
||||
"github.com/cloudflare/cloudflared/client"
|
||||
cfdflow "github.com/cloudflare/cloudflared/flow"
|
||||
|
||||
"github.com/cloudflare/cloudflared/datagramsession"
|
||||
@@ -59,7 +60,7 @@ func TestQUICServer(t *testing.T) {
|
||||
err := wsutil.WriteClientBinary(wsBuf, []byte("Hello"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var tests = []struct {
|
||||
tests := []struct {
|
||||
desc string
|
||||
dest string
|
||||
connectionType pogs.ConnectionType
|
||||
@@ -149,7 +150,7 @@ func TestQUICServer(t *testing.T) {
|
||||
for i, test := range tests {
|
||||
test := test // capture range variable
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
// Start a UDP Listener for QUIC.
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
@@ -193,6 +194,7 @@ func (fakeControlStream) ServeControlStream(ctx context.Context, rw io.ReadWrite
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fakeControlStream) IsStopped() bool {
|
||||
return true
|
||||
}
|
||||
@@ -210,7 +212,7 @@ func quicServer(
|
||||
session, err := listener.Accept(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
quicStream, err := session.OpenStreamSync(context.Background())
|
||||
quicStream, err := session.OpenStreamSync(t.Context())
|
||||
require.NoError(t, err)
|
||||
stream := cfdquic.NewSafeStreamCloser(quicStream, defaultQUICTimeout, &log)
|
||||
|
||||
@@ -277,7 +279,7 @@ func (moc *mockOriginProxyWithRequest) ProxyHTTP(w ResponseWriter, tr *tracing.T
|
||||
}
|
||||
|
||||
func TestBuildHTTPRequest(t *testing.T) {
|
||||
var tests = []struct {
|
||||
tests := []struct {
|
||||
name string
|
||||
connectRequest *pogs.ConnectRequest
|
||||
body io.ReadCloser
|
||||
@@ -498,7 +500,7 @@ func TestBuildHTTPRequest(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
test := test // capture range variable
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req, err := buildHTTPRequest(context.Background(), test.connectRequest, test.body, 0, &log)
|
||||
req, err := buildHTTPRequest(t.Context(), test.connectRequest, test.body, 0, &log)
|
||||
require.NoError(t, err)
|
||||
test.req = test.req.WithContext(req.Context())
|
||||
require.Equal(t, test.req, req.Request)
|
||||
@@ -524,7 +526,7 @@ func TestServeUDPSession(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer udpListener.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
|
||||
// Establish QUIC connection with edge
|
||||
edgeQUICSessionChan := make(chan quic.Connection)
|
||||
@@ -605,7 +607,7 @@ func TestCreateUDPConnReuseSourcePort(t *testing.T) {
|
||||
// TestTCPProxy_FlowRateLimited tests if the pogs.ConnectResponse returns the expected error and metadata, when a
|
||||
// new flow is rate limited.
|
||||
func TestTCPProxy_FlowRateLimited(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
|
||||
// Start a UDP Listener for QUIC.
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||||
@@ -626,7 +628,7 @@ func TestTCPProxy_FlowRateLimited(t *testing.T) {
|
||||
session, err := quicListener.Accept(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
quicStream, err := session.OpenStreamSync(context.Background())
|
||||
quicStream, err := session.OpenStreamSync(t.Context())
|
||||
assert.NoError(t, err)
|
||||
stream := cfdquic.NewSafeStreamCloser(quicStream, defaultQUICTimeout, &log)
|
||||
|
||||
@@ -687,9 +689,7 @@ func testCreateUDPConnReuseSourcePortForEdgeIP(t *testing.T, edgeIP netip.AddrPo
|
||||
}
|
||||
|
||||
func serveSession(ctx context.Context, datagramConn *datagramV2Connection, edgeQUICSession quic.Connection, closeType closeReason, expectedReason string, t *testing.T) {
|
||||
var (
|
||||
payload = []byte(t.Name())
|
||||
)
|
||||
payload := []byte(t.Name())
|
||||
sessionID := uuid.New()
|
||||
cfdConn, originConn := net.Pipe()
|
||||
// Registers and run a new session
|
||||
@@ -802,7 +802,7 @@ func testTunnelConnection(t *testing.T, serverAddr netip.AddrPort, index uint8)
|
||||
}
|
||||
// Start a mock httpProxy
|
||||
log := zerolog.New(io.Discard)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
|
||||
// Dial the QUIC connection to the edge
|
||||
@@ -843,7 +843,7 @@ func testTunnelConnection(t *testing.T, serverAddr netip.AddrPort, index uint8)
|
||||
&mockOrchestrator{originProxy: &mockOriginProxyWithRequest{}},
|
||||
datagramConn,
|
||||
fakeControlStream{},
|
||||
&pogs.ConnectionOptions{},
|
||||
&client.ConnectionOptionsSnapshot{},
|
||||
15*time.Second,
|
||||
0*time.Second,
|
||||
0*time.Second,
|
||||
|
||||
@@ -16,8 +16,7 @@ import (
|
||||
"github.com/cloudflare/cloudflared/mocks"
|
||||
)
|
||||
|
||||
type mockQuicConnection struct {
|
||||
}
|
||||
type mockQuicConnection struct{}
|
||||
|
||||
func (m *mockQuicConnection) AcceptStream(_ context.Context) (quic.Stream, error) {
|
||||
return nil, nil
|
||||
@@ -71,6 +70,10 @@ func (m *mockQuicConnection) ReceiveDatagram(_ context.Context) ([]byte, error)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockQuicConnection) AddPath(*quic.Transport) (*quic.Path, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestRateLimitOnNewDatagramV2UDPSession(t *testing.T) {
|
||||
log := zerolog.Nop()
|
||||
conn := &mockQuicConnection{}
|
||||
@@ -78,7 +81,7 @@ func TestRateLimitOnNewDatagramV2UDPSession(t *testing.T) {
|
||||
flowLimiterMock := mocks.NewMockLimiter(ctrl)
|
||||
|
||||
datagramConn := NewDatagramV2Connection(
|
||||
context.Background(),
|
||||
t.Context(),
|
||||
conn,
|
||||
nil,
|
||||
0,
|
||||
@@ -91,6 +94,6 @@ func TestRateLimitOnNewDatagramV2UDPSession(t *testing.T) {
|
||||
flowLimiterMock.EXPECT().Acquire("udp").Return(cfdflow.ErrTooManyActiveFlows)
|
||||
flowLimiterMock.EXPECT().Release().Times(0)
|
||||
|
||||
_, err := datagramConn.RegisterUdpSession(context.Background(), uuid.New(), net.IPv4(0, 0, 0, 0), 1000, 1*time.Second, "")
|
||||
_, err := datagramConn.RegisterUdpSession(t.Context(), uuid.New(), net.IPv4(0, 0, 0, 0), 1000, 1*time.Second, "")
|
||||
require.ErrorIs(t, err, cfdflow.ErrTooManyActiveFlows)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package connection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
@@ -16,10 +16,17 @@ import (
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsupportedRPCUDPRegistration = errors.New("datagram v3 does not support RegisterUdpSession RPC")
|
||||
ErrUnsupportedRPCUDPUnregistration = errors.New("datagram v3 does not support UnregisterUdpSession RPC")
|
||||
)
|
||||
|
||||
type datagramV3Connection struct {
|
||||
conn quic.Connection
|
||||
conn quic.Connection
|
||||
index uint8
|
||||
// datagramMuxer mux/demux datagrams from quic connection
|
||||
datagramMuxer cfdquic.DatagramConn
|
||||
metrics cfdquic.Metrics
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
|
||||
@@ -40,7 +47,9 @@ func NewDatagramV3Connection(ctx context.Context,
|
||||
|
||||
return &datagramV3Connection{
|
||||
conn,
|
||||
index,
|
||||
datagramMuxer,
|
||||
metrics,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
@@ -50,9 +59,11 @@ func (d *datagramV3Connection) Serve(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (d *datagramV3Connection) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration, traceContext string) (*pogs.RegisterUdpSessionResponse, error) {
|
||||
return nil, fmt.Errorf("datagram v3 does not support RegisterUdpSession RPC")
|
||||
d.metrics.UnsupportedRemoteCommand(d.index, "register_udp_session")
|
||||
return nil, ErrUnsupportedRPCUDPRegistration
|
||||
}
|
||||
|
||||
func (d *datagramV3Connection) UnregisterUdpSession(ctx context.Context, sessionID uuid.UUID, message string) error {
|
||||
return fmt.Errorf("datagram v3 does not support UnregisterUdpSession RPC")
|
||||
d.metrics.UnsupportedRemoteCommand(d.index, "unregister_udp_session")
|
||||
return ErrUnsupportedRPCUDPUnregistration
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ func (s *Session) waitForCloseCondition(ctx context.Context, closeAfterIdle time
|
||||
// Closing dstConn cancels read so dstToTransport routine in Serve() can return
|
||||
defer s.dstConn.Close()
|
||||
if closeAfterIdle == 0 {
|
||||
// provide deafult is caller doesn't specify one
|
||||
// provide default is caller doesn't specify one
|
||||
closeAfterIdle = defaultCloseIdleAfter
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
@@ -34,12 +35,10 @@ func TestCloseIdle(t *testing.T) {
|
||||
}
|
||||
|
||||
func testSessionReturns(t *testing.T, closeBy closeMethod, closeAfterIdle time.Duration) {
|
||||
var (
|
||||
localCloseReason = &errClosedSession{
|
||||
message: "connection closed by origin",
|
||||
byRemote: false,
|
||||
}
|
||||
)
|
||||
localCloseReason := &errClosedSession{
|
||||
message: "connection closed by origin",
|
||||
byRemote: false,
|
||||
}
|
||||
sessionID := uuid.New()
|
||||
cfdConn, originConn := net.Pipe()
|
||||
payload := testPayload(sessionID)
|
||||
@@ -48,28 +47,28 @@ func testSessionReturns(t *testing.T, closeBy closeMethod, closeAfterIdle time.D
|
||||
mg := NewManager(&log, nil, nil)
|
||||
session := mg.newSession(sessionID, cfdConn)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
sessionDone := make(chan struct{})
|
||||
go func() {
|
||||
closedByRemote, err := session.Serve(ctx, closeAfterIdle)
|
||||
switch closeBy {
|
||||
case closeByContext:
|
||||
require.Equal(t, context.Canceled, err)
|
||||
require.False(t, closedByRemote)
|
||||
assert.Equal(t, context.Canceled, err)
|
||||
assert.False(t, closedByRemote)
|
||||
case closeByCallingClose:
|
||||
require.Equal(t, localCloseReason, err)
|
||||
require.Equal(t, localCloseReason.byRemote, closedByRemote)
|
||||
assert.Equal(t, localCloseReason, err)
|
||||
assert.Equal(t, localCloseReason.byRemote, closedByRemote)
|
||||
case closeByTimeout:
|
||||
require.Equal(t, SessionIdleErr(closeAfterIdle), err)
|
||||
require.False(t, closedByRemote)
|
||||
assert.Equal(t, SessionIdleErr(closeAfterIdle), err)
|
||||
assert.False(t, closedByRemote)
|
||||
}
|
||||
close(sessionDone)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
n, err := session.transportToDst(payload)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(payload), n)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(payload), n)
|
||||
}()
|
||||
|
||||
readBuffer := make([]byte, len(payload)+1)
|
||||
@@ -84,6 +83,8 @@ func testSessionReturns(t *testing.T, closeBy closeMethod, closeAfterIdle time.D
|
||||
cancel()
|
||||
case closeByCallingClose:
|
||||
session.close(localCloseReason)
|
||||
default:
|
||||
// ignore
|
||||
}
|
||||
|
||||
<-sessionDone
|
||||
@@ -125,10 +126,10 @@ func testActiveSessionNotClosed(t *testing.T, readFromDst bool, writeToDst bool)
|
||||
|
||||
startTime := time.Now()
|
||||
activeUntil := startTime.Add(activeTime)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
errGroup, ctx := errgroup.WithContext(ctx)
|
||||
errGroup.Go(func() error {
|
||||
session.Serve(ctx, closeAfterIdle)
|
||||
_, _ = session.Serve(ctx, closeAfterIdle)
|
||||
if time.Now().Before(startTime.Add(activeTime)) {
|
||||
return fmt.Errorf("session closed while it's still active")
|
||||
}
|
||||
@@ -208,7 +209,7 @@ func TestZeroBytePayload(t *testing.T) {
|
||||
mg := NewManager(&nopLogger, sender.muxSession, nil)
|
||||
session := mg.newSession(sessionID, cfdConn)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
errGroup, ctx := errgroup.WithContext(ctx)
|
||||
errGroup.Go(func() error {
|
||||
// Read from underlying conn and send to transport
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
FROM golang:1.22.10 as builder
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0
|
||||
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
RUN apt-get update
|
||||
COPY . .
|
||||
RUN .teamcity/install-cloudflare-go.sh
|
||||
# compile cloudflared
|
||||
RUN PATH="/tmp/go/bin:$PATH" make cloudflared
|
||||
RUN cp /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
||||
+29
-7
@@ -1,5 +1,7 @@
|
||||
package features
|
||||
|
||||
import "slices"
|
||||
|
||||
const (
|
||||
FeatureSerializedHeaders = "serialized_headers"
|
||||
FeatureQuickReconnects = "quick_reconnects"
|
||||
@@ -8,7 +10,9 @@ const (
|
||||
FeaturePostQuantum = "postquantum"
|
||||
FeatureQUICSupportEOF = "support_quic_eof"
|
||||
FeatureManagementLogs = "management_logs"
|
||||
FeatureDatagramV3 = "support_datagram_v3"
|
||||
FeatureDatagramV3_1 = "support_datagram_v3_1"
|
||||
|
||||
DeprecatedFeatureDatagramV3 = "support_datagram_v3" // Deprecated: TUN-9291
|
||||
)
|
||||
|
||||
var defaultFeatures = []string{
|
||||
@@ -19,11 +23,25 @@ var defaultFeatures = []string{
|
||||
FeatureManagementLogs,
|
||||
}
|
||||
|
||||
// List of features that are no longer in-use.
|
||||
var deprecatedFeatures = []string{
|
||||
DeprecatedFeatureDatagramV3,
|
||||
}
|
||||
|
||||
// Features set by user provided flags
|
||||
type staticFeatures struct {
|
||||
PostQuantumMode *PostQuantumMode
|
||||
}
|
||||
|
||||
type FeatureSnapshot struct {
|
||||
PostQuantum PostQuantumMode
|
||||
DatagramVersion DatagramVersion
|
||||
|
||||
// We provide the list of features since we need it to send in the ConnectionOptions during connection
|
||||
// registrations.
|
||||
FeaturesList []string
|
||||
}
|
||||
|
||||
type PostQuantumMode uint8
|
||||
|
||||
const (
|
||||
@@ -40,15 +58,19 @@ const (
|
||||
// DatagramV2 is the currently supported datagram protocol for UDP and ICMP packets
|
||||
DatagramV2 DatagramVersion = FeatureDatagramV2
|
||||
// DatagramV3 is a new datagram protocol for UDP and ICMP packets. It is not backwards compatible with datagram v2.
|
||||
DatagramV3 DatagramVersion = FeatureDatagramV3
|
||||
DatagramV3 DatagramVersion = FeatureDatagramV3_1
|
||||
)
|
||||
|
||||
// Remove any duplicates from the slice
|
||||
func Dedup(slice []string) []string {
|
||||
// Remove any duplicate features from the list and remove deprecated features
|
||||
func dedupAndRemoveFeatures(features []string) []string {
|
||||
// Convert the slice into a set
|
||||
set := make(map[string]bool, 0)
|
||||
for _, str := range slice {
|
||||
set[str] = true
|
||||
set := map[string]bool{}
|
||||
for _, feature := range features {
|
||||
// Remove deprecated features from the provided list
|
||||
if slices.Contains(deprecatedFeatures, feature) {
|
||||
continue
|
||||
}
|
||||
set[feature] = true
|
||||
}
|
||||
|
||||
// Convert the set back into a slice
|
||||
|
||||
+57
-46
@@ -15,28 +15,31 @@ import (
|
||||
|
||||
const (
|
||||
featureSelectorHostname = "cfd-features.argotunnel.com"
|
||||
defaultRefreshFreq = time.Hour * 6
|
||||
lookupTimeout = time.Second * 10
|
||||
defaultLookupFreq = time.Hour
|
||||
)
|
||||
|
||||
// If the TXT record adds other fields, the umarshal logic will ignore those keys
|
||||
// If the TXT record is missing a key, the field will unmarshal to the default Go value
|
||||
|
||||
type featuresRecord struct {
|
||||
// support_datagram_v3
|
||||
DatagramV3Percentage int32 `json:"dv3"`
|
||||
DatagramV3Percentage uint32 `json:"dv3_1"`
|
||||
|
||||
// DatagramV3Percentage int32 `json:"dv3"` // Removed in TUN-9291
|
||||
// PostQuantumPercentage int32 `json:"pq"` // Removed in TUN-7970
|
||||
}
|
||||
|
||||
func NewFeatureSelector(ctx context.Context, accountTag string, cliFeatures []string, pq bool, logger *zerolog.Logger) (*FeatureSelector, error) {
|
||||
return newFeatureSelector(ctx, accountTag, logger, newDNSResolver(), cliFeatures, pq, defaultRefreshFreq)
|
||||
func NewFeatureSelector(ctx context.Context, accountTag string, cliFeatures []string, pq bool, logger *zerolog.Logger) (FeatureSelector, error) {
|
||||
return newFeatureSelector(ctx, accountTag, logger, newDNSResolver(), cliFeatures, pq, defaultLookupFreq)
|
||||
}
|
||||
|
||||
// FeatureSelector determines if this account will try new features. It periodically queries a DNS TXT record
|
||||
// to see which features are turned on.
|
||||
type FeatureSelector struct {
|
||||
accountHash int32
|
||||
type FeatureSelector interface {
|
||||
Snapshot() FeatureSnapshot
|
||||
}
|
||||
|
||||
// FeatureSelector determines if this account will try new features; loaded once during startup.
|
||||
type featureSelector struct {
|
||||
accountHash uint32
|
||||
logger *zerolog.Logger
|
||||
resolver resolver
|
||||
|
||||
@@ -44,11 +47,11 @@ type FeatureSelector struct {
|
||||
cliFeatures []string
|
||||
|
||||
// lock protects concurrent access to dynamic features
|
||||
lock sync.RWMutex
|
||||
features featuresRecord
|
||||
lock sync.RWMutex
|
||||
remoteFeatures featuresRecord
|
||||
}
|
||||
|
||||
func newFeatureSelector(ctx context.Context, accountTag string, logger *zerolog.Logger, resolver resolver, cliFeatures []string, pq bool, refreshFreq time.Duration) (*FeatureSelector, error) {
|
||||
func newFeatureSelector(ctx context.Context, accountTag string, logger *zerolog.Logger, resolver resolver, cliFeatures []string, pq bool, refreshFreq time.Duration) (*featureSelector, error) {
|
||||
// Combine default features and user-provided features
|
||||
var pqMode *PostQuantumMode
|
||||
if pq {
|
||||
@@ -59,28 +62,40 @@ func newFeatureSelector(ctx context.Context, accountTag string, logger *zerolog.
|
||||
staticFeatures := staticFeatures{
|
||||
PostQuantumMode: pqMode,
|
||||
}
|
||||
selector := &FeatureSelector{
|
||||
selector := &featureSelector{
|
||||
accountHash: switchThreshold(accountTag),
|
||||
logger: logger,
|
||||
resolver: resolver,
|
||||
staticFeatures: staticFeatures,
|
||||
cliFeatures: Dedup(cliFeatures),
|
||||
cliFeatures: dedupAndRemoveFeatures(cliFeatures),
|
||||
}
|
||||
|
||||
// Load the remote features
|
||||
if err := selector.refresh(ctx); err != nil {
|
||||
logger.Err(err).Msg("Failed to fetch features, default to disable")
|
||||
}
|
||||
|
||||
// Spin off reloading routine
|
||||
go selector.refreshLoop(ctx, refreshFreq)
|
||||
|
||||
return selector, nil
|
||||
}
|
||||
|
||||
func (fs *FeatureSelector) accountEnabled(percentage int32) bool {
|
||||
func (fs *featureSelector) Snapshot() FeatureSnapshot {
|
||||
fs.lock.RLock()
|
||||
defer fs.lock.RUnlock()
|
||||
return FeatureSnapshot{
|
||||
PostQuantum: fs.postQuantumMode(),
|
||||
DatagramVersion: fs.datagramVersion(),
|
||||
FeaturesList: fs.clientFeatures(),
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *featureSelector) accountEnabled(percentage uint32) bool {
|
||||
return percentage > fs.accountHash
|
||||
}
|
||||
|
||||
func (fs *FeatureSelector) PostQuantumMode() PostQuantumMode {
|
||||
func (fs *featureSelector) postQuantumMode() PostQuantumMode {
|
||||
if fs.staticFeatures.PostQuantumMode != nil {
|
||||
return *fs.staticFeatures.PostQuantumMode
|
||||
}
|
||||
@@ -88,12 +103,9 @@ func (fs *FeatureSelector) PostQuantumMode() PostQuantumMode {
|
||||
return PostQuantumPrefer
|
||||
}
|
||||
|
||||
func (fs *FeatureSelector) DatagramVersion() DatagramVersion {
|
||||
fs.lock.RLock()
|
||||
defer fs.lock.RUnlock()
|
||||
|
||||
func (fs *featureSelector) datagramVersion() DatagramVersion {
|
||||
// If user provides the feature via the cli, we take it as priority over remote feature evaluation
|
||||
if slices.Contains(fs.cliFeatures, FeatureDatagramV3) {
|
||||
if slices.Contains(fs.cliFeatures, FeatureDatagramV3_1) {
|
||||
return DatagramV3
|
||||
}
|
||||
// If the user specifies DatagramV2, we also take that over remote
|
||||
@@ -101,36 +113,20 @@ func (fs *FeatureSelector) DatagramVersion() DatagramVersion {
|
||||
return DatagramV2
|
||||
}
|
||||
|
||||
if fs.accountEnabled(fs.features.DatagramV3Percentage) {
|
||||
if fs.accountEnabled(fs.remoteFeatures.DatagramV3Percentage) {
|
||||
return DatagramV3
|
||||
}
|
||||
|
||||
return DatagramV2
|
||||
}
|
||||
|
||||
// ClientFeatures will return the list of currently available features that cloudflared should provide to the edge.
|
||||
//
|
||||
// This list is dynamic and can change in-between returns.
|
||||
func (fs *FeatureSelector) ClientFeatures() []string {
|
||||
// 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
|
||||
return Dedup(slices.Concat(defaultFeatures, fs.cliFeatures, []string{string(fs.DatagramVersion())}))
|
||||
return dedupAndRemoveFeatures(slices.Concat(defaultFeatures, fs.cliFeatures, []string{string(fs.datagramVersion())}))
|
||||
}
|
||||
|
||||
func (fs *FeatureSelector) refreshLoop(ctx context.Context, refreshFreq time.Duration) {
|
||||
ticker := time.NewTicker(refreshFreq)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
err := fs.refresh(ctx)
|
||||
if err != nil {
|
||||
fs.logger.Err(err).Msg("Failed to refresh feature selector")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *FeatureSelector) refresh(ctx context.Context) error {
|
||||
func (fs *featureSelector) refresh(ctx context.Context) error {
|
||||
record, err := fs.resolver.lookupRecord(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -144,11 +140,26 @@ func (fs *FeatureSelector) refresh(ctx context.Context) error {
|
||||
fs.lock.Lock()
|
||||
defer fs.lock.Unlock()
|
||||
|
||||
fs.features = features
|
||||
fs.remoteFeatures = features
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *featureSelector) refreshLoop(ctx context.Context, refreshFreq time.Duration) {
|
||||
ticker := time.NewTicker(refreshFreq)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
err := fs.refresh(ctx)
|
||||
if err != nil {
|
||||
fs.logger.Err(err).Msg("Failed to refresh feature selector")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resolver represents an object that can look up featuresRecord
|
||||
type resolver interface {
|
||||
lookupRecord(ctx context.Context) ([]byte, error)
|
||||
@@ -180,8 +191,8 @@ func (dr *dnsResolver) lookupRecord(ctx context.Context) ([]byte, error) {
|
||||
return []byte(records[0]), nil
|
||||
}
|
||||
|
||||
func switchThreshold(accountTag string) int32 {
|
||||
func switchThreshold(accountTag string) uint32 {
|
||||
h := fnv.New32a()
|
||||
_, _ = h.Write([]byte(accountTag))
|
||||
return int32(h.Sum32() % 100)
|
||||
return h.Sum32() % 100
|
||||
}
|
||||
|
||||
+96
-64
@@ -11,21 +11,26 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
testAccountTag = "123456"
|
||||
testAccountHash = 74 // switchThreshold of `accountTag`
|
||||
)
|
||||
|
||||
func TestUnmarshalFeaturesRecord(t *testing.T) {
|
||||
tests := []struct {
|
||||
record []byte
|
||||
expectedPercentage int32
|
||||
expectedPercentage uint32
|
||||
}{
|
||||
{
|
||||
record: []byte(`{"dv3":0}`),
|
||||
record: []byte(`{"dv3_1":0}`),
|
||||
expectedPercentage: 0,
|
||||
},
|
||||
{
|
||||
record: []byte(`{"dv3":39}`),
|
||||
record: []byte(`{"dv3_1":39}`),
|
||||
expectedPercentage: 39,
|
||||
},
|
||||
{
|
||||
record: []byte(`{"dv3":100}`),
|
||||
record: []byte(`{"dv3_1":100}`),
|
||||
expectedPercentage: 100,
|
||||
},
|
||||
{
|
||||
@@ -34,6 +39,9 @@ func TestUnmarshalFeaturesRecord(t *testing.T) {
|
||||
{
|
||||
record: []byte(`{"kyber":768}`), // Unmarshal to default struct if key is not present
|
||||
},
|
||||
{
|
||||
record: []byte(`{"pq": 101,"dv3":100}`), // Expired keys don't unmarshal to anything
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -61,7 +69,7 @@ func TestFeaturePrecedenceEvaluationPostQuantum(t *testing.T) {
|
||||
{
|
||||
name: "user_specified",
|
||||
cli: true,
|
||||
expectedFeatures: Dedup(append(defaultFeatures, FeaturePostQuantum)),
|
||||
expectedFeatures: dedupAndRemoveFeatures(append(defaultFeatures, FeaturePostQuantum)),
|
||||
expectedVersion: PostQuantumStrict,
|
||||
},
|
||||
}
|
||||
@@ -69,10 +77,11 @@ func TestFeaturePrecedenceEvaluationPostQuantum(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
resolver := &staticResolver{record: featuresRecord{}}
|
||||
selector, err := newFeatureSelector(context.Background(), test.name, &logger, resolver, []string{}, test.cli, time.Second)
|
||||
selector, err := newFeatureSelector(t.Context(), test.name, &logger, resolver, []string{}, test.cli, time.Second)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, test.expectedFeatures, selector.ClientFeatures())
|
||||
require.Equal(t, test.expectedVersion, selector.PostQuantumMode())
|
||||
snapshot := selector.Snapshot()
|
||||
require.ElementsMatch(t, test.expectedFeatures, snapshot.FeaturesList)
|
||||
require.Equal(t, test.expectedVersion, snapshot.PostQuantum)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -102,97 +111,120 @@ func TestFeaturePrecedenceEvaluationDatagramVersion(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "user_specified_v3",
|
||||
cli: []string{FeatureDatagramV3},
|
||||
cli: []string{FeatureDatagramV3_1},
|
||||
remote: featuresRecord{},
|
||||
expectedFeatures: Dedup(append(defaultFeatures, FeatureDatagramV3)),
|
||||
expectedVersion: FeatureDatagramV3,
|
||||
},
|
||||
{
|
||||
name: "remote_specified_v3",
|
||||
cli: []string{},
|
||||
remote: featuresRecord{
|
||||
DatagramV3Percentage: 100,
|
||||
},
|
||||
expectedFeatures: Dedup(append(defaultFeatures, FeatureDatagramV3)),
|
||||
expectedVersion: FeatureDatagramV3,
|
||||
},
|
||||
{
|
||||
name: "remote_and_user_specified_v3",
|
||||
cli: []string{FeatureDatagramV3},
|
||||
remote: featuresRecord{
|
||||
DatagramV3Percentage: 100,
|
||||
},
|
||||
expectedFeatures: Dedup(append(defaultFeatures, FeatureDatagramV3)),
|
||||
expectedVersion: FeatureDatagramV3,
|
||||
},
|
||||
{
|
||||
name: "remote_v3_and_user_specified_v2",
|
||||
cli: []string{FeatureDatagramV2},
|
||||
remote: featuresRecord{
|
||||
DatagramV3Percentage: 100,
|
||||
},
|
||||
expectedFeatures: defaultFeatures,
|
||||
expectedVersion: DatagramV2,
|
||||
expectedFeatures: dedupAndRemoveFeatures(append(defaultFeatures, FeatureDatagramV3_1)),
|
||||
expectedVersion: FeatureDatagramV3_1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
resolver := &staticResolver{record: test.remote}
|
||||
selector, err := newFeatureSelector(context.Background(), test.name, &logger, resolver, test.cli, false, time.Second)
|
||||
selector, err := newFeatureSelector(t.Context(), test.name, &logger, resolver, test.cli, false, time.Second)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, test.expectedFeatures, selector.ClientFeatures())
|
||||
require.Equal(t, test.expectedVersion, selector.DatagramVersion())
|
||||
snapshot := selector.Snapshot()
|
||||
require.ElementsMatch(t, test.expectedFeatures, snapshot.FeaturesList)
|
||||
require.Equal(t, test.expectedVersion, snapshot.DatagramVersion)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeprecatedFeaturesRemoved(t *testing.T) {
|
||||
logger := zerolog.Nop()
|
||||
tests := []struct {
|
||||
name string
|
||||
cli []string
|
||||
remote featuresRecord
|
||||
expectedFeatures []string
|
||||
}{
|
||||
{
|
||||
name: "no_removals",
|
||||
cli: []string{},
|
||||
remote: featuresRecord{},
|
||||
expectedFeatures: defaultFeatures,
|
||||
},
|
||||
{
|
||||
name: "support_datagram_v3",
|
||||
cli: []string{DeprecatedFeatureDatagramV3},
|
||||
remote: featuresRecord{},
|
||||
expectedFeatures: defaultFeatures,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
resolver := &staticResolver{record: test.remote}
|
||||
selector, err := newFeatureSelector(t.Context(), test.name, &logger, resolver, test.cli, false, time.Second)
|
||||
require.NoError(t, err)
|
||||
snapshot := selector.Snapshot()
|
||||
require.ElementsMatch(t, test.expectedFeatures, snapshot.FeaturesList)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshFeaturesRecord(t *testing.T) {
|
||||
// The hash of the accountTag is 82
|
||||
accountTag := t.Name()
|
||||
threshold := switchThreshold(accountTag)
|
||||
|
||||
percentages := []int32{0, 10, 81, 82, 83, 100, 101, 1000}
|
||||
refreshFreq := time.Millisecond * 10
|
||||
selector := newTestSelector(t, percentages, false, refreshFreq)
|
||||
percentages := []uint32{0, 10, testAccountHash - 1, testAccountHash, testAccountHash + 1, 100, 101, 1000}
|
||||
selector := newTestSelector(t, percentages, false, time.Minute)
|
||||
|
||||
// Starting out should default to DatagramV2
|
||||
require.Equal(t, DatagramV2, selector.DatagramVersion())
|
||||
snapshot := selector.Snapshot()
|
||||
require.Equal(t, DatagramV2, snapshot.DatagramVersion)
|
||||
|
||||
for _, percentage := range percentages {
|
||||
if percentage > threshold {
|
||||
require.Equal(t, DatagramV3, selector.DatagramVersion())
|
||||
snapshot = selector.Snapshot()
|
||||
if percentage > testAccountHash {
|
||||
require.Equal(t, DatagramV3, snapshot.DatagramVersion)
|
||||
} else {
|
||||
require.Equal(t, DatagramV2, selector.DatagramVersion())
|
||||
require.Equal(t, DatagramV2, snapshot.DatagramVersion)
|
||||
}
|
||||
|
||||
time.Sleep(refreshFreq + time.Millisecond)
|
||||
// Manually progress the next refresh
|
||||
_ = selector.refresh(t.Context())
|
||||
}
|
||||
|
||||
// Make sure error doesn't override the last fetched features
|
||||
require.Equal(t, DatagramV3, selector.DatagramVersion())
|
||||
// Make sure a resolver error doesn't override the last fetched features
|
||||
snapshot = selector.Snapshot()
|
||||
require.Equal(t, DatagramV3, snapshot.DatagramVersion)
|
||||
}
|
||||
|
||||
func TestSnapshotIsolation(t *testing.T) {
|
||||
percentages := []uint32{testAccountHash, testAccountHash + 1}
|
||||
selector := newTestSelector(t, percentages, false, time.Minute)
|
||||
|
||||
// Starting out should default to DatagramV2
|
||||
snapshot := selector.Snapshot()
|
||||
require.Equal(t, DatagramV2, snapshot.DatagramVersion)
|
||||
|
||||
// Manually progress the next refresh
|
||||
_ = selector.refresh(t.Context())
|
||||
|
||||
snapshot2 := selector.Snapshot()
|
||||
require.Equal(t, DatagramV3, snapshot2.DatagramVersion)
|
||||
require.NotEqual(t, snapshot.DatagramVersion, snapshot2.DatagramVersion)
|
||||
}
|
||||
|
||||
func TestStaticFeatures(t *testing.T) {
|
||||
percentages := []int32{0}
|
||||
percentages := []uint32{0}
|
||||
// PostQuantum Enabled from user flag
|
||||
selector := newTestSelector(t, percentages, true, time.Millisecond*10)
|
||||
require.Equal(t, PostQuantumStrict, selector.PostQuantumMode())
|
||||
selector := newTestSelector(t, percentages, true, time.Second)
|
||||
snapshot := selector.Snapshot()
|
||||
require.Equal(t, PostQuantumStrict, snapshot.PostQuantum)
|
||||
|
||||
// PostQuantum Disabled (or not set)
|
||||
selector = newTestSelector(t, percentages, false, time.Millisecond*10)
|
||||
require.Equal(t, PostQuantumPrefer, selector.PostQuantumMode())
|
||||
selector = newTestSelector(t, percentages, false, time.Second)
|
||||
snapshot = selector.Snapshot()
|
||||
require.Equal(t, PostQuantumPrefer, snapshot.PostQuantum)
|
||||
}
|
||||
|
||||
func newTestSelector(t *testing.T, percentages []int32, pq bool, refreshFreq time.Duration) *FeatureSelector {
|
||||
accountTag := t.Name()
|
||||
func newTestSelector(t *testing.T, percentages []uint32, pq bool, refreshFreq time.Duration) *featureSelector {
|
||||
logger := zerolog.Nop()
|
||||
|
||||
resolver := &mockResolver{
|
||||
percentages: percentages,
|
||||
}
|
||||
|
||||
selector, err := newFeatureSelector(context.Background(), accountTag, &logger, resolver, []string{}, pq, refreshFreq)
|
||||
selector, err := newFeatureSelector(t.Context(), testAccountTag, &logger, resolver, []string{}, pq, refreshFreq)
|
||||
require.NoError(t, err)
|
||||
|
||||
return selector
|
||||
@@ -200,7 +232,7 @@ func newTestSelector(t *testing.T, percentages []int32, pq bool, refreshFreq tim
|
||||
|
||||
type mockResolver struct {
|
||||
nextIndex int
|
||||
percentages []int32
|
||||
percentages []uint32
|
||||
}
|
||||
|
||||
func (mr *mockResolver) lookupRecord(ctx context.Context) ([]byte, error) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/cloudflare/cloudflared
|
||||
|
||||
go 1.22
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/coredns/coredns v1.11.3
|
||||
@@ -12,7 +12,7 @@ require (
|
||||
github.com/getsentry/sentry-go v0.16.0
|
||||
github.com/go-chi/chi/v5 v5.0.8
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-jose/go-jose/v4 v4.0.1
|
||||
github.com/go-jose/go-jose/v4 v4.1.0
|
||||
github.com/gobwas/ws v1.2.1
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/uuid v1.6.0
|
||||
@@ -24,9 +24,9 @@ require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/prometheus/client_model v0.6.0
|
||||
github.com/quic-go/quic-go v0.45.0
|
||||
github.com/quic-go/quic-go v0.51.0
|
||||
github.com/rs/zerolog v1.20.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
go.opentelemetry.io/contrib/propagators v0.22.0
|
||||
go.opentelemetry.io/otel v1.26.0
|
||||
@@ -34,14 +34,14 @@ require (
|
||||
go.opentelemetry.io/otel/sdk v1.26.0
|
||||
go.opentelemetry.io/otel/trace v1.26.0
|
||||
go.opentelemetry.io/proto/otlp v1.2.0
|
||||
go.uber.org/automaxprocs v1.4.0
|
||||
go.uber.org/mock v0.5.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/sys v0.28.0
|
||||
golang.org/x/term v0.27.0
|
||||
google.golang.org/protobuf v1.34.1
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
go.uber.org/mock v0.5.1
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/net v0.39.0
|
||||
golang.org/x/sync v0.13.0
|
||||
golang.org/x/sys v0.32.0
|
||||
golang.org/x/term v0.31.0
|
||||
google.golang.org/protobuf v1.36.5
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
nhooyr.io/websocket v1.8.7
|
||||
@@ -61,35 +61,32 @@ require (
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect
|
||||
github.com/google/pprof v0.0.0-20250418163039-24c5476c6587 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/common v0.53.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/oauth2 v0.18.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/oauth2 v0.29.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/tools v0.32.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
google.golang.org/grpc v1.63.2 // indirect
|
||||
@@ -104,4 +101,4 @@ replace github.com/prometheus/golang_client => github.com/prometheus/golang_clie
|
||||
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
// This fork is based on quic-go v0.45
|
||||
replace github.com/quic-go/quic-go => github.com/chungthuang/quic-go v0.45.1-0.20250128102735-2687bd175910
|
||||
replace github.com/quic-go/quic-go => github.com/chungthuang/quic-go v0.45.1-0.20250428085412-43229ad201fd
|
||||
|
||||
@@ -7,8 +7,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chungthuang/quic-go v0.45.1-0.20250128102735-2687bd175910 h1:/hTvBpxBDj/3NIzTodi1oEOyNBpirvgDSPKSV7VqAZU=
|
||||
github.com/chungthuang/quic-go v0.45.1-0.20250128102735-2687bd175910/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||
github.com/chungthuang/quic-go v0.45.1-0.20250428085412-43229ad201fd h1:VdYI5zFQ2h1/qzoC6rhyPx479bkF8i177Qpg4Q2n1vk=
|
||||
github.com/chungthuang/quic-go v0.45.1-0.20250428085412-43229ad201fd/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
|
||||
github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
|
||||
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
|
||||
github.com/coredns/coredns v1.11.3 h1:8RjnpZc42db5th84/QJKH2i137ecJdzZK1HJwhetSPk=
|
||||
@@ -21,7 +21,6 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@@ -55,11 +54,11 @@ github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
|
||||
github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
@@ -72,8 +71,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
||||
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
@@ -89,20 +88,17 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo=
|
||||
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20250418163039-24c5476c6587 h1:b/8HpQhvKLSNzH5oTXN2WkNcMl6YB5K3FRbb+i+Ml34=
|
||||
github.com/google/pprof v0.0.0-20250418163039-24c5476c6587/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -121,11 +117,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
|
||||
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
@@ -150,10 +143,10 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||
@@ -167,6 +160,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
||||
@@ -187,10 +182,9 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
||||
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
@@ -198,7 +192,6 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/contrib/propagators v0.22.0 h1:KGdv58M2//veiYLIhb31mofaI2LgkIPXXAZVeYVyfd8=
|
||||
go.opentelemetry.io/contrib/propagators v0.22.0/go.mod h1:xGOuXr6lLIF9BXipA4pm6UuOSI0M98U6tsI3khbOiwU=
|
||||
go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM=
|
||||
@@ -215,85 +208,60 @@ go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2L
|
||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
||||
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
|
||||
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
|
||||
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build windows
|
||||
//go:build windows && cgo
|
||||
|
||||
package ingress
|
||||
|
||||
|
||||
+4
-4
@@ -113,7 +113,7 @@ func ParseIngressFromConfigAndCLI(conf *config.Configuration, c *cli.Context, lo
|
||||
// If no token is provided, the probability of NOT being a remotely managed tunnel is higher.
|
||||
// So, we should warn the user that no ingress rules were found, because remote configuration will most likely not exist.
|
||||
if !c.IsSet("token") {
|
||||
log.Warn().Msgf(ErrNoIngressRulesCLI.Error())
|
||||
log.Warn().Msg(ErrNoIngressRulesCLI.Error())
|
||||
}
|
||||
return newDefaultOrigin(c, log), nil
|
||||
}
|
||||
@@ -378,17 +378,17 @@ func validateHostname(r config.UnvalidatedIngressRule, ruleIndex, totalRules int
|
||||
}
|
||||
// ONLY the last rule should catch all hostnames.
|
||||
if !isLastRule && isCatchAllRule {
|
||||
return errRuleShouldNotBeCatchAll{index: ruleIndex, hostname: r.Hostname}
|
||||
return ruleShouldNotBeCatchAllError{index: ruleIndex, hostname: r.Hostname}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type errRuleShouldNotBeCatchAll struct {
|
||||
type ruleShouldNotBeCatchAllError struct {
|
||||
index int
|
||||
hostname string
|
||||
}
|
||||
|
||||
func (e errRuleShouldNotBeCatchAll) Error() string {
|
||||
func (e ruleShouldNotBeCatchAllError) Error() string {
|
||||
return fmt.Sprintf("Rule #%d is matching the hostname '%s', but "+
|
||||
"this will match every hostname, meaning the rules which follow it "+
|
||||
"will never be triggered.", e.index+1, e.hostname)
|
||||
|
||||
@@ -66,7 +66,7 @@ func (o *httpService) SetOriginServerName(req *http.Request) {
|
||||
}
|
||||
return tls.Client(conn, &tls.Config{
|
||||
RootCAs: o.transport.TLSClientConfig.RootCAs,
|
||||
InsecureSkipVerify: o.transport.TLSClientConfig.InsecureSkipVerify,
|
||||
InsecureSkipVerify: o.transport.TLSClientConfig.InsecureSkipVerify, // nolint: gosec
|
||||
ServerName: req.Host,
|
||||
}), nil
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func (o *httpService) SetOriginServerName(req *http.Request) {
|
||||
|
||||
func (o *statusCode) RoundTrip(_ *http.Request) (*http.Response, error) {
|
||||
if o.defaultResp {
|
||||
o.log.Warn().Msgf(ErrNoIngressRulesCLI.Error())
|
||||
o.log.Warn().Msg(ErrNoIngressRulesCLI.Error())
|
||||
}
|
||||
resp := &http.Response{
|
||||
StatusCode: o.code,
|
||||
@@ -114,7 +114,6 @@ func (o *tcpOverWSService) EstablishConnection(ctx context.Context, dest string,
|
||||
streamHandler: o.streamHandler,
|
||||
}
|
||||
return originConn, nil
|
||||
|
||||
}
|
||||
|
||||
func (o *socksProxyOverWSService) EstablishConnection(_ context.Context, _ string, _ *zerolog.Logger) (OriginConnection, error) {
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestUpdateConfiguration(t *testing.T) {
|
||||
initConfig := &Config{
|
||||
Ingress: &ingress.Ingress{},
|
||||
}
|
||||
orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, []ingress.Rule{ingress.NewManagementRule(management.New("management.argotunnel.com", false, "1.1.1.1:80", uuid.Nil, "", &testLogger, nil))}, &testLogger)
|
||||
orchestrator, err := NewOrchestrator(t.Context(), initConfig, testTags, []ingress.Rule{ingress.NewManagementRule(management.New("management.argotunnel.com", false, "1.1.1.1:80", uuid.Nil, "", &testLogger, nil))}, &testLogger)
|
||||
require.NoError(t, err)
|
||||
initOriginProxy, err := orchestrator.GetOriginProxy()
|
||||
require.NoError(t, err)
|
||||
@@ -182,7 +182,7 @@ func TestUpdateConfiguration_FromMigration(t *testing.T) {
|
||||
initConfig := &Config{
|
||||
Ingress: &ingress.Ingress{},
|
||||
}
|
||||
orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, []ingress.Rule{}, &testLogger)
|
||||
orchestrator, err := NewOrchestrator(t.Context(), initConfig, testTags, []ingress.Rule{}, &testLogger)
|
||||
require.NoError(t, err)
|
||||
initOriginProxy, err := orchestrator.GetOriginProxy()
|
||||
require.NoError(t, err)
|
||||
@@ -208,7 +208,7 @@ func TestUpdateConfiguration_WithoutIngressRule(t *testing.T) {
|
||||
initConfig := &Config{
|
||||
Ingress: &ingress.Ingress{},
|
||||
}
|
||||
orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, []ingress.Rule{}, &testLogger)
|
||||
orchestrator, err := NewOrchestrator(t.Context(), initConfig, testTags, []ingress.Rule{}, &testLogger)
|
||||
require.NoError(t, err)
|
||||
initOriginProxy, err := orchestrator.GetOriginProxy()
|
||||
require.NoError(t, err)
|
||||
@@ -300,7 +300,7 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
|
||||
}
|
||||
)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
|
||||
orchestrator, err := NewOrchestrator(ctx, initConfig, testTags, []ingress.Rule{}, &testLogger)
|
||||
@@ -393,7 +393,7 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
|
||||
// TestOverrideWarpRoutingConfigWithLocalValues tests that if a value is defined in the Config.ConfigurationFlags,
|
||||
// it will override the value that comes from the remote result.
|
||||
func TestOverrideWarpRoutingConfigWithLocalValues(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
|
||||
assertMaxActiveFlows := func(orchestrator *Orchestrator, expectedValue uint64) {
|
||||
@@ -580,7 +580,7 @@ func TestClosePreviousProxies(t *testing.T) {
|
||||
}
|
||||
)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
orchestrator, err := NewOrchestrator(ctx, initConfig, testTags, []ingress.Rule{}, &testLogger)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -641,7 +641,7 @@ func TestPersistentConnection(t *testing.T) {
|
||||
initConfig := &Config{
|
||||
Ingress: &ingress.Ingress{},
|
||||
}
|
||||
orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, []ingress.Rule{}, &testLogger)
|
||||
orchestrator, err := NewOrchestrator(t.Context(), initConfig, testTags, []ingress.Rule{}, &testLogger)
|
||||
require.NoError(t, err)
|
||||
|
||||
wsOrigin := httptest.NewServer(http.HandlerFunc(wsEcho))
|
||||
@@ -674,7 +674,7 @@ func TestPersistentConnection(t *testing.T) {
|
||||
tcpReqReader, tcpReqWriter := io.Pipe()
|
||||
tcpRespReadWriter := newRespReadWriteFlusher()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
+9
-8
@@ -148,7 +148,7 @@ func (w *mockSSERespWriter) ReadBytes() []byte {
|
||||
func TestProxySingleOrigin(t *testing.T) {
|
||||
log := zerolog.Nop()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
|
||||
flagSet := flag.NewFlagSet(t.Name(), flag.PanicOnError)
|
||||
flagSet.Bool("hello-world", true, "")
|
||||
@@ -190,7 +190,7 @@ func testProxyWebsocket(proxy connection.OriginProxy) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
// WSRoute is a websocket echo handler
|
||||
const testTimeout = 5 * time.Second * 1000
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), testTimeout)
|
||||
defer cancel()
|
||||
readPipe, writePipe := io.Pipe()
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://localhost:8080%s", hello.WSRoute), readPipe)
|
||||
@@ -257,7 +257,7 @@ func testProxySSE(proxy connection.OriginProxy) func(t *testing.T) {
|
||||
pushFreq = time.Millisecond * 10
|
||||
)
|
||||
responseWriter := newMockSSERespWriter()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://localhost:8080%s?freq=%s", hello.SSERoute, pushFreq), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -365,7 +365,7 @@ func runIngressTestScenarios(t *testing.T, unvalidatedIngress []config.Unvalidat
|
||||
|
||||
log := zerolog.Nop()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
require.NoError(t, ingress.StartOrigins(&log, ctx.Done()))
|
||||
|
||||
proxy := NewOriginProxy(ingress, noWarpRouting, testTags, cfdflow.NewLimiter(0), time.Duration(0), &log)
|
||||
@@ -494,7 +494,7 @@ func TestConnections(t *testing.T) {
|
||||
err bool
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
@@ -686,7 +686,7 @@ func TestConnections(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
// Starts origin service
|
||||
@@ -755,6 +755,7 @@ func newWSRequestBody(data []byte) *requestBody {
|
||||
pw: pw,
|
||||
}
|
||||
}
|
||||
|
||||
func newTCPRequestBody(data []byte) *requestBody {
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
@@ -973,12 +974,12 @@ func runEchoTCPService(t *testing.T, l net.Listener) {
|
||||
}
|
||||
|
||||
func runEchoWSService(t *testing.T, l net.Listener) {
|
||||
var upgrader = gorillaWS.Upgrader{
|
||||
upgrader := gorillaWS.Upgrader{
|
||||
ReadBufferSize: 10,
|
||||
WriteBufferSize: 10,
|
||||
}
|
||||
|
||||
var ws = func(w http.ResponseWriter, r *http.Request) {
|
||||
ws := func(w http.ResponseWriter, r *http.Request) {
|
||||
header := make(http.Header)
|
||||
for k, vs := range r.Header {
|
||||
if k == "Test-Cloudflared-Echo" {
|
||||
|
||||
+30
-27
@@ -11,12 +11,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
namespace = "quic"
|
||||
namespace = "quic"
|
||||
ConnectionIndexMetricLabel = "conn_index"
|
||||
frameTypeMetricLabel = "frame_type"
|
||||
packetTypeMetricLabel = "packet_type"
|
||||
reasonMetricLabel = "reason"
|
||||
)
|
||||
|
||||
var (
|
||||
clientConnLabels = []string{"conn_index"}
|
||||
clientMetrics = struct {
|
||||
clientMetrics = struct {
|
||||
totalConnections prometheus.Counter
|
||||
closedConnections prometheus.Counter
|
||||
maxUDPPayloadSize *prometheus.GaugeVec
|
||||
@@ -35,7 +38,7 @@ var (
|
||||
congestionState *prometheus.GaugeVec
|
||||
}{
|
||||
totalConnections: prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: "client",
|
||||
Name: "total_connections",
|
||||
@@ -43,7 +46,7 @@ var (
|
||||
},
|
||||
),
|
||||
closedConnections: prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: "client",
|
||||
Name: "closed_connections",
|
||||
@@ -57,70 +60,70 @@ var (
|
||||
Name: "max_udp_payload",
|
||||
Help: "Maximum UDP payload size in bytes for a QUIC packet",
|
||||
},
|
||||
clientConnLabels,
|
||||
[]string{ConnectionIndexMetricLabel},
|
||||
),
|
||||
sentFrames: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: "client",
|
||||
Name: "sent_frames",
|
||||
Help: "Number of frames that have been sent through a connection",
|
||||
},
|
||||
append(clientConnLabels, "frame_type"),
|
||||
[]string{ConnectionIndexMetricLabel, frameTypeMetricLabel},
|
||||
),
|
||||
sentBytes: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: "client",
|
||||
Name: "sent_bytes",
|
||||
Help: "Number of bytes that have been sent through a connection",
|
||||
},
|
||||
clientConnLabels,
|
||||
[]string{ConnectionIndexMetricLabel},
|
||||
),
|
||||
receivedFrames: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: "client",
|
||||
Name: "received_frames",
|
||||
Help: "Number of frames that have been received through a connection",
|
||||
},
|
||||
append(clientConnLabels, "frame_type"),
|
||||
[]string{ConnectionIndexMetricLabel, frameTypeMetricLabel},
|
||||
),
|
||||
receivedBytes: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: "client",
|
||||
Name: "receive_bytes",
|
||||
Help: "Number of bytes that have been received through a connection",
|
||||
},
|
||||
clientConnLabels,
|
||||
[]string{ConnectionIndexMetricLabel},
|
||||
),
|
||||
bufferedPackets: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: "client",
|
||||
Name: "buffered_packets",
|
||||
Help: "Number of bytes that have been buffered on a connection",
|
||||
},
|
||||
append(clientConnLabels, "packet_type"),
|
||||
[]string{ConnectionIndexMetricLabel, packetTypeMetricLabel},
|
||||
),
|
||||
droppedPackets: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: "client",
|
||||
Name: "dropped_packets",
|
||||
Help: "Number of bytes that have been dropped on a connection",
|
||||
},
|
||||
append(clientConnLabels, "packet_type", "reason"),
|
||||
[]string{ConnectionIndexMetricLabel, packetTypeMetricLabel, reasonMetricLabel},
|
||||
),
|
||||
lostPackets: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: "client",
|
||||
Name: "lost_packets",
|
||||
Help: "Number of packets that have been lost from a connection",
|
||||
},
|
||||
append(clientConnLabels, "reason"),
|
||||
[]string{ConnectionIndexMetricLabel, reasonMetricLabel},
|
||||
),
|
||||
minRTT: prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
@@ -129,7 +132,7 @@ var (
|
||||
Name: "min_rtt",
|
||||
Help: "Lowest RTT measured on a connection in millisec",
|
||||
},
|
||||
clientConnLabels,
|
||||
[]string{ConnectionIndexMetricLabel},
|
||||
),
|
||||
latestRTT: prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
@@ -138,7 +141,7 @@ var (
|
||||
Name: "latest_rtt",
|
||||
Help: "Latest RTT measured on a connection",
|
||||
},
|
||||
clientConnLabels,
|
||||
[]string{ConnectionIndexMetricLabel},
|
||||
),
|
||||
smoothedRTT: prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
@@ -147,7 +150,7 @@ var (
|
||||
Name: "smoothed_rtt",
|
||||
Help: "Calculated smoothed RTT measured on a connection in millisec",
|
||||
},
|
||||
clientConnLabels,
|
||||
[]string{ConnectionIndexMetricLabel},
|
||||
),
|
||||
mtu: prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
@@ -156,7 +159,7 @@ var (
|
||||
Name: "mtu",
|
||||
Help: "Current maximum transmission unit (MTU) of a connection",
|
||||
},
|
||||
clientConnLabels,
|
||||
[]string{ConnectionIndexMetricLabel},
|
||||
),
|
||||
congestionWindow: prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
@@ -165,7 +168,7 @@ var (
|
||||
Name: "congestion_window",
|
||||
Help: "Current congestion window size",
|
||||
},
|
||||
clientConnLabels,
|
||||
[]string{ConnectionIndexMetricLabel},
|
||||
),
|
||||
congestionState: prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
@@ -174,13 +177,13 @@ var (
|
||||
Name: "congestion_state",
|
||||
Help: "Current congestion control state. See https://pkg.go.dev/github.com/quic-go/quic-go@v0.45.0/logging#CongestionState for what each value maps to",
|
||||
},
|
||||
clientConnLabels,
|
||||
[]string{ConnectionIndexMetricLabel},
|
||||
),
|
||||
}
|
||||
|
||||
registerClient = sync.Once{}
|
||||
|
||||
packetTooBigDropped = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
packetTooBigDropped = prometheus.NewCounter(prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: "client",
|
||||
Name: "packet_too_big_dropped",
|
||||
|
||||
+50
-31
@@ -1,83 +1,101 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/cloudflare/cloudflared/quic"
|
||||
)
|
||||
|
||||
const (
|
||||
namespace = "cloudflared"
|
||||
subsystem = "udp"
|
||||
|
||||
commandMetricLabel = "command"
|
||||
)
|
||||
|
||||
type Metrics interface {
|
||||
IncrementFlows()
|
||||
DecrementFlows()
|
||||
PayloadTooLarge()
|
||||
RetryFlowResponse()
|
||||
MigrateFlow()
|
||||
IncrementFlows(connIndex uint8)
|
||||
DecrementFlows(connIndex uint8)
|
||||
PayloadTooLarge(connIndex uint8)
|
||||
RetryFlowResponse(connIndex uint8)
|
||||
MigrateFlow(connIndex uint8)
|
||||
UnsupportedRemoteCommand(connIndex uint8, command string)
|
||||
}
|
||||
|
||||
type metrics struct {
|
||||
activeUDPFlows prometheus.Gauge
|
||||
totalUDPFlows prometheus.Counter
|
||||
payloadTooLarge prometheus.Counter
|
||||
retryFlowResponses prometheus.Counter
|
||||
migratedFlows prometheus.Counter
|
||||
activeUDPFlows *prometheus.GaugeVec
|
||||
totalUDPFlows *prometheus.CounterVec
|
||||
payloadTooLarge *prometheus.CounterVec
|
||||
retryFlowResponses *prometheus.CounterVec
|
||||
migratedFlows *prometheus.CounterVec
|
||||
unsupportedRemoteCommands *prometheus.CounterVec
|
||||
}
|
||||
|
||||
func (m *metrics) IncrementFlows() {
|
||||
m.totalUDPFlows.Inc()
|
||||
m.activeUDPFlows.Inc()
|
||||
func (m *metrics) IncrementFlows(connIndex uint8) {
|
||||
m.totalUDPFlows.WithLabelValues(fmt.Sprintf("%d", connIndex)).Inc()
|
||||
m.activeUDPFlows.WithLabelValues(fmt.Sprintf("%d", connIndex)).Inc()
|
||||
}
|
||||
|
||||
func (m *metrics) DecrementFlows() {
|
||||
m.activeUDPFlows.Dec()
|
||||
func (m *metrics) DecrementFlows(connIndex uint8) {
|
||||
m.activeUDPFlows.WithLabelValues(fmt.Sprintf("%d", connIndex)).Dec()
|
||||
}
|
||||
|
||||
func (m *metrics) PayloadTooLarge() {
|
||||
m.payloadTooLarge.Inc()
|
||||
func (m *metrics) PayloadTooLarge(connIndex uint8) {
|
||||
m.payloadTooLarge.WithLabelValues(fmt.Sprintf("%d", connIndex)).Inc()
|
||||
}
|
||||
|
||||
func (m *metrics) RetryFlowResponse() {
|
||||
m.retryFlowResponses.Inc()
|
||||
func (m *metrics) RetryFlowResponse(connIndex uint8) {
|
||||
m.retryFlowResponses.WithLabelValues(fmt.Sprintf("%d", connIndex)).Inc()
|
||||
}
|
||||
|
||||
func (m *metrics) MigrateFlow() {
|
||||
m.migratedFlows.Inc()
|
||||
func (m *metrics) MigrateFlow(connIndex uint8) {
|
||||
m.migratedFlows.WithLabelValues(fmt.Sprintf("%d", connIndex)).Inc()
|
||||
}
|
||||
|
||||
func (m *metrics) UnsupportedRemoteCommand(connIndex uint8, command string) {
|
||||
m.unsupportedRemoteCommands.WithLabelValues(fmt.Sprintf("%d", connIndex), command).Inc()
|
||||
}
|
||||
|
||||
func NewMetrics(registerer prometheus.Registerer) Metrics {
|
||||
m := &metrics{
|
||||
activeUDPFlows: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
activeUDPFlows: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "active_flows",
|
||||
Help: "Concurrent count of UDP flows that are being proxied to any origin",
|
||||
}),
|
||||
totalUDPFlows: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
}, []string{quic.ConnectionIndexMetricLabel}),
|
||||
totalUDPFlows: prometheus.NewCounterVec(prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "total_flows",
|
||||
Help: "Total count of UDP flows that have been proxied to any origin",
|
||||
}),
|
||||
payloadTooLarge: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
}, []string{quic.ConnectionIndexMetricLabel}),
|
||||
payloadTooLarge: prometheus.NewCounterVec(prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "payload_too_large",
|
||||
Help: "Total count of UDP flows that have had origin payloads that are too large to proxy",
|
||||
}),
|
||||
retryFlowResponses: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
}, []string{quic.ConnectionIndexMetricLabel}),
|
||||
retryFlowResponses: prometheus.NewCounterVec(prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "retry_flow_responses",
|
||||
Help: "Total count of UDP flows that have had to send their registration response more than once",
|
||||
}),
|
||||
migratedFlows: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
}, []string{quic.ConnectionIndexMetricLabel}),
|
||||
migratedFlows: prometheus.NewCounterVec(prometheus.CounterOpts{ //nolint:promlinter
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "migrated_flows",
|
||||
Help: "Total count of UDP flows have been migrated across local connections",
|
||||
}),
|
||||
}, []string{quic.ConnectionIndexMetricLabel}),
|
||||
unsupportedRemoteCommands: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "unsupported_remote_command_total",
|
||||
Help: "Total count of unsupported remote RPC commands for the ",
|
||||
}, []string{quic.ConnectionIndexMetricLabel, commandMetricLabel}),
|
||||
}
|
||||
registerer.MustRegister(
|
||||
m.activeUDPFlows,
|
||||
@@ -85,6 +103,7 @@ func NewMetrics(registerer prometheus.Registerer) Metrics {
|
||||
m.payloadTooLarge,
|
||||
m.retryFlowResponses,
|
||||
m.migratedFlows,
|
||||
m.unsupportedRemoteCommands,
|
||||
)
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ package v3_test
|
||||
|
||||
type noopMetrics struct{}
|
||||
|
||||
func (noopMetrics) IncrementFlows() {}
|
||||
func (noopMetrics) DecrementFlows() {}
|
||||
func (noopMetrics) PayloadTooLarge() {}
|
||||
func (noopMetrics) RetryFlowResponse() {}
|
||||
func (noopMetrics) MigrateFlow() {}
|
||||
func (noopMetrics) IncrementFlows(connIndex uint8) {}
|
||||
func (noopMetrics) DecrementFlows(connIndex uint8) {}
|
||||
func (noopMetrics) PayloadTooLarge(connIndex uint8) {}
|
||||
func (noopMetrics) RetryFlowResponse(connIndex uint8) {}
|
||||
func (noopMetrics) MigrateFlow(connIndex uint8) {}
|
||||
func (noopMetrics) UnsupportedRemoteCommand(connIndex uint8, command string) {}
|
||||
|
||||
+3
-3
@@ -264,10 +264,10 @@ func (c *datagramConn) handleSessionRegistrationDatagram(ctx context.Context, da
|
||||
return
|
||||
}
|
||||
log = log.With().Str(logSrcKey, session.LocalAddr().String()).Logger()
|
||||
c.metrics.IncrementFlows()
|
||||
c.metrics.IncrementFlows(c.index)
|
||||
// Make sure to eventually remove the session from the session manager when the session is closed
|
||||
defer c.sessionManager.UnregisterSession(session.ID())
|
||||
defer c.metrics.DecrementFlows()
|
||||
defer c.metrics.DecrementFlows(c.index)
|
||||
|
||||
// Respond that we are able to process the new session
|
||||
err = c.SendUDPSessionResponse(datagram.RequestID, ResponseOk)
|
||||
@@ -315,7 +315,7 @@ func (c *datagramConn) handleSessionAlreadyRegistered(requestID RequestID, logge
|
||||
// The session is already running in another routine so we want to restart the idle timeout since no proxied
|
||||
// packets have come down yet.
|
||||
session.ResetIdleTimer()
|
||||
c.metrics.RetryFlowResponse()
|
||||
c.metrics.RetryFlowResponse(c.index)
|
||||
logger.Debug().Msgf("flow registration response retry")
|
||||
}
|
||||
|
||||
|
||||
+18
-18
@@ -136,7 +136,7 @@ func TestDatagramConnServe_ApplicationClosed(t *testing.T) {
|
||||
quic := newMockQuicConn()
|
||||
conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, ingress.DialUDPAddrPort, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 1*time.Second)
|
||||
defer cancel()
|
||||
err := conn.Serve(ctx)
|
||||
if !errors.Is(err, context.DeadlineExceeded) {
|
||||
@@ -147,12 +147,12 @@ func TestDatagramConnServe_ApplicationClosed(t *testing.T) {
|
||||
func TestDatagramConnServe_ConnectionClosed(t *testing.T) {
|
||||
log := zerolog.Nop()
|
||||
quic := newMockQuicConn()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 1*time.Second)
|
||||
defer cancel()
|
||||
quic.ctx = ctx
|
||||
conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, ingress.DialUDPAddrPort, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log)
|
||||
|
||||
err := conn.Serve(context.Background())
|
||||
err := conn.Serve(t.Context())
|
||||
if !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -163,7 +163,7 @@ func TestDatagramConnServe_ReceiveDatagramError(t *testing.T) {
|
||||
quic := &mockQuicConnReadError{err: net.ErrClosed}
|
||||
conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, ingress.DialUDPAddrPort, cfdflow.NewLimiter(0)), &noopICMPRouter{}, 0, &noopMetrics{}, &log)
|
||||
|
||||
err := conn.Serve(context.Background())
|
||||
err := conn.Serve(t.Context())
|
||||
if !errors.Is(err, net.ErrClosed) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -178,7 +178,7 @@ func TestDatagramConnServe_SessionRegistrationRateLimit(t *testing.T) {
|
||||
conn := v3.NewDatagramConn(quic, sessionManager, &noopICMPRouter{}, 0, &noopMetrics{}, &log)
|
||||
|
||||
// Setup the muxer
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
@@ -230,7 +230,7 @@ func TestDatagramConnServe_ErrorDatagramTypes(t *testing.T) {
|
||||
quic.send <- test.input
|
||||
conn := v3.NewDatagramConn(quic, &mockSessionManager{}, &noopICMPRouter{}, 0, &noopMetrics{}, &log)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 1*time.Second)
|
||||
defer cancel()
|
||||
err := conn.Serve(ctx)
|
||||
// we cancel the Serve method to check to see if the log output was written since the unsupported datagram
|
||||
@@ -272,7 +272,7 @@ func TestDatagramConnServe_RegisterSession_SessionManagerError(t *testing.T) {
|
||||
conn := v3.NewDatagramConn(quic, &sessionManager, &noopICMPRouter{}, 0, &noopMetrics{}, &log)
|
||||
|
||||
// Setup the muxer
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
ctx, cancel := context.WithCancelCause(t.Context())
|
||||
defer cancel(errors.New("other error"))
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
@@ -307,7 +307,7 @@ func TestDatagramConnServe(t *testing.T) {
|
||||
conn := v3.NewDatagramConn(quic, &sessionManager, &noopICMPRouter{}, 0, &noopMetrics{}, &log)
|
||||
|
||||
// Setup the muxer
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
ctx, cancel := context.WithCancelCause(t.Context())
|
||||
defer cancel(errors.New("other error"))
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
@@ -356,7 +356,7 @@ func TestDatagramConnServeDecodeMultipleICMPInParallel(t *testing.T) {
|
||||
conn := v3.NewDatagramConn(quic, &sessionManager, router, 0, &noopMetrics{}, &log)
|
||||
|
||||
// Setup the muxer
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
ctx, cancel := context.WithCancelCause(t.Context())
|
||||
defer cancel(errors.New("other error"))
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
@@ -435,7 +435,7 @@ func TestDatagramConnServe_RegisterTwice(t *testing.T) {
|
||||
conn := v3.NewDatagramConn(quic, &sessionManager, &noopICMPRouter{}, 0, &noopMetrics{}, &log)
|
||||
|
||||
// Setup the muxer
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
ctx, cancel := context.WithCancelCause(t.Context())
|
||||
defer cancel(errors.New("other error"))
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
@@ -499,14 +499,14 @@ func TestDatagramConnServe_MigrateConnection(t *testing.T) {
|
||||
conn2 := v3.NewDatagramConn(quic2, &sessionManager, &noopICMPRouter{}, 1, &noopMetrics{}, &log)
|
||||
|
||||
// Setup the muxer
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
ctx, cancel := context.WithCancelCause(t.Context())
|
||||
defer cancel(errors.New("other error"))
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- conn.Serve(ctx)
|
||||
}()
|
||||
|
||||
ctx2, cancel2 := context.WithCancelCause(context.Background())
|
||||
ctx2, cancel2 := context.WithCancelCause(t.Context())
|
||||
defer cancel2(errors.New("other error"))
|
||||
done2 := make(chan error, 1)
|
||||
go func() {
|
||||
@@ -580,7 +580,7 @@ func TestDatagramConnServe_Payload_GetSessionError(t *testing.T) {
|
||||
conn := v3.NewDatagramConn(quic, &sessionManager, &noopICMPRouter{}, 0, &noopMetrics{}, &log)
|
||||
|
||||
// Setup the muxer
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
ctx, cancel := context.WithCancelCause(t.Context())
|
||||
defer cancel(errors.New("other error"))
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
@@ -608,7 +608,7 @@ func TestDatagramConnServe_Payload(t *testing.T) {
|
||||
conn := v3.NewDatagramConn(quic, &sessionManager, &noopICMPRouter{}, 0, &noopMetrics{}, &log)
|
||||
|
||||
// Setup the muxer
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
ctx, cancel := context.WithCancelCause(t.Context())
|
||||
defer cancel(errors.New("other error"))
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
@@ -637,7 +637,7 @@ func TestDatagramConnServe_ICMPDatagram_TTLDecremented(t *testing.T) {
|
||||
conn := v3.NewDatagramConn(quic, &mockSessionManager{}, router, 0, &noopMetrics{}, &log)
|
||||
|
||||
// Setup the muxer
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
ctx, cancel := context.WithCancelCause(t.Context())
|
||||
defer cancel(errors.New("other error"))
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
@@ -683,7 +683,7 @@ func TestDatagramConnServe_ICMPDatagram_TTLExceeded(t *testing.T) {
|
||||
conn := v3.NewDatagramConn(quic, &mockSessionManager{}, router, 0, &noopMetrics{}, &log)
|
||||
|
||||
// Setup the muxer
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
ctx, cancel := context.WithCancelCause(t.Context())
|
||||
defer cancel(errors.New("other error"))
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
@@ -781,12 +781,12 @@ func newICMPDatagram(pk *packet.ICMP) []byte {
|
||||
|
||||
// Cancel the provided context and make sure it closes with the expected cancellation error
|
||||
func assertContextClosed(t *testing.T, ctx context.Context, done <-chan error, cancel context.CancelCauseFunc) {
|
||||
cancel(expectedContextCanceled)
|
||||
cancel(errExpectedContextCanceled)
|
||||
err := <-done
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !errors.Is(context.Cause(ctx), expectedContextCanceled) {
|
||||
if !errors.Is(context.Cause(ctx), errExpectedContextCanceled) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-6
@@ -27,11 +27,11 @@ const (
|
||||
)
|
||||
|
||||
// SessionCloseErr indicates that the session's Close method was called.
|
||||
var SessionCloseErr error = errors.New("flow was closed directly")
|
||||
var SessionCloseErr error = errors.New("flow was closed directly") //nolint:errname
|
||||
|
||||
// SessionIdleErr is returned when the session was closed because there was no communication
|
||||
// in either direction over the session for the timeout period.
|
||||
type SessionIdleErr struct {
|
||||
type SessionIdleErr struct { //nolint:errname
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
@@ -149,7 +149,8 @@ func (s *session) Migrate(eyeball DatagramConn, ctx context.Context, logger *zer
|
||||
}
|
||||
// The session is already running so we want to restart the idle timeout since no proxied packets have come down yet.
|
||||
s.markActive()
|
||||
s.metrics.MigrateFlow()
|
||||
connectionIndex := eyeball.ID()
|
||||
s.metrics.MigrateFlow(connectionIndex)
|
||||
}
|
||||
|
||||
func (s *session) Serve(ctx context.Context) error {
|
||||
@@ -160,7 +161,7 @@ func (s *session) Serve(ctx context.Context) error {
|
||||
// To perform a zero copy write when passing the datagram to the connection, we prepare the buffer with
|
||||
// the required datagram header information. We can reuse this buffer for this session since the header is the
|
||||
// same for the each read.
|
||||
MarshalPayloadHeaderTo(s.id, readBuffer[:DatagramPayloadHeaderLen])
|
||||
_ = MarshalPayloadHeaderTo(s.id, readBuffer[:DatagramPayloadHeaderLen])
|
||||
for {
|
||||
// Read from the origin UDP socket
|
||||
n, err := s.origin.Read(readBuffer[DatagramPayloadHeaderLen:])
|
||||
@@ -177,7 +178,8 @@ func (s *session) Serve(ctx context.Context) error {
|
||||
continue
|
||||
}
|
||||
if n > maxDatagramPayloadLen {
|
||||
s.metrics.PayloadTooLarge()
|
||||
connectionIndex := s.ConnectionID()
|
||||
s.metrics.PayloadTooLarge(connectionIndex)
|
||||
s.log.Error().Int(logPacketSizeKey, n).Msg("flow (origin) packet read was too large and was dropped")
|
||||
continue
|
||||
}
|
||||
@@ -241,7 +243,7 @@ func (s *session) waitForCloseCondition(ctx context.Context, closeAfterIdle time
|
||||
// Closing the session at the end cancels read so Serve() can return
|
||||
defer s.Close()
|
||||
if closeAfterIdle == 0 {
|
||||
// provide deafult is caller doesn't specify one
|
||||
// Provided that the default caller doesn't specify one
|
||||
closeAfterIdle = defaultCloseIdleAfter
|
||||
}
|
||||
|
||||
|
||||
+17
-17
@@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
expectedContextCanceled = errors.New("expected context canceled")
|
||||
errExpectedContextCanceled = errors.New("expected context canceled")
|
||||
|
||||
testOriginAddr = net.UDPAddrFromAddrPort(netip.MustParseAddrPort("127.0.0.1:0"))
|
||||
testLocalAddr = net.UDPAddrFromAddrPort(netip.MustParseAddrPort("127.0.0.1:0"))
|
||||
@@ -40,7 +40,7 @@ func testSessionWrite(t *testing.T, payload []byte) {
|
||||
serverRead := make(chan []byte, 1)
|
||||
go func() {
|
||||
read := make([]byte, 1500)
|
||||
server.Read(read[:])
|
||||
_, _ = server.Read(read[:])
|
||||
serverRead <- read
|
||||
}()
|
||||
// Create session and write to origin
|
||||
@@ -93,7 +93,7 @@ func testSessionServe_Origin(t *testing.T, payload []byte) {
|
||||
session := v3.NewSession(testRequestID, 3*time.Second, origin, testOriginAddr, testLocalAddr, &eyeball, &noopMetrics{}, &log)
|
||||
defer session.Close()
|
||||
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
ctx, cancel := context.WithCancelCause(t.Context())
|
||||
defer cancel(context.Canceled)
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
@@ -110,12 +110,12 @@ func testSessionServe_Origin(t *testing.T, payload []byte) {
|
||||
case data := <-eyeball.recvData:
|
||||
// check received data matches provided from origin
|
||||
expectedData := makePayload(1500)
|
||||
v3.MarshalPayloadHeaderTo(testRequestID, expectedData[:])
|
||||
_ = v3.MarshalPayloadHeaderTo(testRequestID, expectedData[:])
|
||||
copy(expectedData[17:], payload)
|
||||
if !slices.Equal(expectedData[:v3.DatagramPayloadHeaderLen+len(payload)], data) {
|
||||
t.Fatal("expected datagram did not equal expected")
|
||||
}
|
||||
cancel(expectedContextCanceled)
|
||||
cancel(errExpectedContextCanceled)
|
||||
case err := <-ctx.Done():
|
||||
// we expect the payload to return before the context to cancel on the session
|
||||
t.Fatal(err)
|
||||
@@ -125,7 +125,7 @@ func testSessionServe_Origin(t *testing.T, payload []byte) {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !errors.Is(context.Cause(ctx), expectedContextCanceled) {
|
||||
if !errors.Is(context.Cause(ctx), errExpectedContextCanceled) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ func TestSessionServe_OriginTooLarge(t *testing.T) {
|
||||
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
done <- session.Serve(context.Background())
|
||||
done <- session.Serve(t.Context())
|
||||
}()
|
||||
|
||||
// Attempt to write a payload too large from the origin
|
||||
@@ -173,7 +173,7 @@ func TestSessionServe_Migrate(t *testing.T) {
|
||||
defer session.Close()
|
||||
|
||||
done := make(chan error)
|
||||
eyeball1Ctx, cancel := context.WithCancelCause(context.Background())
|
||||
eyeball1Ctx, cancel := context.WithCancelCause(t.Context())
|
||||
go func() {
|
||||
done <- session.Serve(eyeball1Ctx)
|
||||
}()
|
||||
@@ -181,7 +181,7 @@ func TestSessionServe_Migrate(t *testing.T) {
|
||||
// Migrate the session to a new connection before origin sends data
|
||||
eyeball2 := newMockEyeball()
|
||||
eyeball2.connID = 1
|
||||
eyeball2Ctx := context.Background()
|
||||
eyeball2Ctx := t.Context()
|
||||
session.Migrate(&eyeball2, eyeball2Ctx, &log)
|
||||
|
||||
// Cancel the origin eyeball context; this should not cancel the session
|
||||
@@ -198,7 +198,7 @@ func TestSessionServe_Migrate(t *testing.T) {
|
||||
|
||||
// Origin sends data
|
||||
payload2 := []byte{0xde}
|
||||
pipe1.Write(payload2)
|
||||
_, _ = pipe1.Write(payload2)
|
||||
|
||||
// Expect write to eyeball2
|
||||
data := <-eyeball2.recvData
|
||||
@@ -230,7 +230,7 @@ func TestSessionServe_Migrate_CloseContext2(t *testing.T) {
|
||||
defer session.Close()
|
||||
|
||||
done := make(chan error)
|
||||
eyeball1Ctx, cancel := context.WithCancelCause(context.Background())
|
||||
eyeball1Ctx, cancel := context.WithCancelCause(t.Context())
|
||||
go func() {
|
||||
done <- session.Serve(eyeball1Ctx)
|
||||
}()
|
||||
@@ -238,7 +238,7 @@ func TestSessionServe_Migrate_CloseContext2(t *testing.T) {
|
||||
// Migrate the session to a new connection before origin sends data
|
||||
eyeball2 := newMockEyeball()
|
||||
eyeball2.connID = 1
|
||||
eyeball2Ctx, cancel2 := context.WithCancelCause(context.Background())
|
||||
eyeball2Ctx, cancel2 := context.WithCancelCause(t.Context())
|
||||
session.Migrate(&eyeball2, eyeball2Ctx, &log)
|
||||
|
||||
// Cancel the origin eyeball context; this should not cancel the session
|
||||
@@ -249,13 +249,13 @@ func TestSessionServe_Migrate_CloseContext2(t *testing.T) {
|
||||
t.Fatalf("expected session to still be running")
|
||||
default:
|
||||
}
|
||||
if context.Cause(eyeball1Ctx) != contextCancelErr {
|
||||
if !errors.Is(context.Cause(eyeball1Ctx), contextCancelErr) {
|
||||
t.Fatalf("first eyeball context should be cancelled manually: %+v", context.Cause(eyeball1Ctx))
|
||||
}
|
||||
|
||||
// Origin sends data
|
||||
payload2 := []byte{0xde}
|
||||
pipe1.Write(payload2)
|
||||
_, _ = pipe1.Write(payload2)
|
||||
|
||||
// Expect write to eyeball2
|
||||
data := <-eyeball2.recvData
|
||||
@@ -316,7 +316,7 @@ func TestSessionServe_IdleTimeout(t *testing.T) {
|
||||
defer server.Close()
|
||||
closeAfterIdle := 2 * time.Second
|
||||
session := v3.NewSession(testRequestID, closeAfterIdle, origin, testOriginAddr, testLocalAddr, &noopEyeball{}, &noopMetrics{}, &log)
|
||||
err := session.Serve(context.Background())
|
||||
err := session.Serve(t.Context())
|
||||
if !errors.Is(err, v3.SessionIdleErr{}) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -342,7 +342,7 @@ func TestSessionServe_ParentContextCanceled(t *testing.T) {
|
||||
closeAfterIdle := 10 * time.Second
|
||||
|
||||
session := v3.NewSession(testRequestID, closeAfterIdle, origin, testOriginAddr, testLocalAddr, &noopEyeball{}, &noopMetrics{}, &log)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second)
|
||||
defer cancel()
|
||||
err := session.Serve(ctx)
|
||||
if !errors.Is(err, context.DeadlineExceeded) {
|
||||
@@ -366,7 +366,7 @@ func TestSessionServe_ReadErrors(t *testing.T) {
|
||||
log := zerolog.Nop()
|
||||
origin := newTestErrOrigin(net.ErrClosed, nil)
|
||||
session := v3.NewSession(testRequestID, 30*time.Second, &origin, testOriginAddr, testLocalAddr, &noopEyeball{}, &noopMetrics{}, &log)
|
||||
err := session.Serve(context.Background())
|
||||
err := session.Serve(t.Context())
|
||||
if !errors.Is(err, net.ErrClosed) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+17
-23
@@ -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
|
||||
|
||||
@@ -96,7 +96,7 @@ func (p *UpdateConfigurationResponse) Unmarshal(s proto.UpdateConfigurationRespo
|
||||
return err
|
||||
}
|
||||
if respErr != "" {
|
||||
p.Err = fmt.Errorf(respErr)
|
||||
p.Err = fmt.Errorf("%s", respErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ func (p *RegisterUdpSessionResponse) Unmarshal(s proto.RegisterUdpSessionRespons
|
||||
return err
|
||||
}
|
||||
if respErr != "" {
|
||||
p.Err = fmt.Errorf(respErr)
|
||||
p.Err = fmt.Errorf("%s", respErr)
|
||||
}
|
||||
p.Spans, err = s.Spans()
|
||||
if err != nil {
|
||||
@@ -164,7 +164,7 @@ func (c SessionManager_PogsClient) RegisterUdpSession(ctx context.Context, sessi
|
||||
}
|
||||
p.SetDstPort(dstPort)
|
||||
p.SetCloseAfterIdleHint(int64(closeAfterIdleHint))
|
||||
p.SetTraceContext(traceContext)
|
||||
_ = p.SetTraceContext(traceContext)
|
||||
return nil
|
||||
})
|
||||
result, err := promise.Result().Struct()
|
||||
|
||||
@@ -22,7 +22,7 @@ const (
|
||||
)
|
||||
|
||||
func TestConnectRequestData(t *testing.T) {
|
||||
var tests = []struct {
|
||||
tests := []struct {
|
||||
name string
|
||||
hostname string
|
||||
connectionType pogs.ConnectionType
|
||||
@@ -62,7 +62,7 @@ func TestConnectRequestData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConnectResponseMeta(t *testing.T) {
|
||||
var tests = []struct {
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
metadata []pogs.Metadata
|
||||
@@ -106,7 +106,7 @@ func TestConnectResponseMeta(t *testing.T) {
|
||||
func TestRegisterUdpSession(t *testing.T) {
|
||||
unregisterMessage := "closed by eyeball"
|
||||
|
||||
var tests = []struct {
|
||||
tests := []struct {
|
||||
name string
|
||||
sessionRPCServer mockSessionRPCServer
|
||||
}{
|
||||
@@ -140,29 +140,29 @@ func TestRegisterUdpSession(t *testing.T) {
|
||||
sessionRegisteredChan := make(chan struct{})
|
||||
go func() {
|
||||
ss := NewCloudflaredServer(nil, test.sessionRPCServer, nil, 10*time.Second)
|
||||
err := ss.Serve(context.Background(), serverStream)
|
||||
err := ss.Serve(t.Context(), serverStream)
|
||||
assert.NoError(t, err)
|
||||
|
||||
serverStream.Close()
|
||||
close(sessionRegisteredChan)
|
||||
}()
|
||||
|
||||
rpcClientStream, err := NewCloudflaredClient(context.Background(), clientStream, 5*time.Second)
|
||||
rpcClientStream, err := NewCloudflaredClient(t.Context(), clientStream, 5*time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
reg, err := rpcClientStream.RegisterUdpSession(context.Background(), test.sessionRPCServer.sessionID, test.sessionRPCServer.dstIP, test.sessionRPCServer.dstPort, testCloseIdleAfterHint, test.sessionRPCServer.traceContext)
|
||||
reg, err := rpcClientStream.RegisterUdpSession(t.Context(), test.sessionRPCServer.sessionID, test.sessionRPCServer.dstIP, test.sessionRPCServer.dstPort, testCloseIdleAfterHint, test.sessionRPCServer.traceContext)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, reg.Err)
|
||||
|
||||
// Different sessionID, the RPC server should reject the registration
|
||||
reg, err = rpcClientStream.RegisterUdpSession(context.Background(), uuid.New(), test.sessionRPCServer.dstIP, test.sessionRPCServer.dstPort, testCloseIdleAfterHint, test.sessionRPCServer.traceContext)
|
||||
reg, err = rpcClientStream.RegisterUdpSession(t.Context(), uuid.New(), test.sessionRPCServer.dstIP, test.sessionRPCServer.dstPort, testCloseIdleAfterHint, test.sessionRPCServer.traceContext)
|
||||
require.NoError(t, err)
|
||||
require.Error(t, reg.Err)
|
||||
|
||||
require.NoError(t, rpcClientStream.UnregisterUdpSession(context.Background(), test.sessionRPCServer.sessionID, unregisterMessage))
|
||||
require.NoError(t, rpcClientStream.UnregisterUdpSession(t.Context(), test.sessionRPCServer.sessionID, unregisterMessage))
|
||||
|
||||
// Different sessionID, the RPC server should reject the unregistration
|
||||
require.Error(t, rpcClientStream.UnregisterUdpSession(context.Background(), uuid.New(), unregisterMessage))
|
||||
require.Error(t, rpcClientStream.UnregisterUdpSession(t.Context(), uuid.New(), unregisterMessage))
|
||||
|
||||
rpcClientStream.Close()
|
||||
<-sessionRegisteredChan
|
||||
@@ -185,14 +185,14 @@ func TestManageConfiguration(t *testing.T) {
|
||||
updatedChan := make(chan struct{})
|
||||
go func() {
|
||||
server := NewCloudflaredServer(nil, nil, configRPCServer, 10*time.Second)
|
||||
err := server.Serve(context.Background(), serverStream)
|
||||
err := server.Serve(t.Context(), serverStream)
|
||||
assert.NoError(t, err)
|
||||
|
||||
serverStream.Close()
|
||||
close(updatedChan)
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), time.Second)
|
||||
defer cancel()
|
||||
rpcClientStream, err := NewCloudflaredClient(ctx, clientStream, 5*time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
+29
@@ -1,3 +1,32 @@
|
||||
## Changed
|
||||
|
||||
- Defined a custom error, ErrUnexpectedSignatureAlgorithm, returned when a JWS
|
||||
header contains an unsupported signature algorithm.
|
||||
|
||||
# v4.0.4
|
||||
|
||||
## Fixed
|
||||
|
||||
- Reverted "Allow unmarshalling JSONWebKeySets with unsupported key types" as a
|
||||
breaking change. See #136 / #137.
|
||||
|
||||
# v4.0.3
|
||||
|
||||
## Changed
|
||||
|
||||
- Allow unmarshalling JSONWebKeySets with unsupported key types (#130)
|
||||
- Document that OpaqueKeyEncrypter can't be implemented (for now) (#129)
|
||||
- Dependency updates
|
||||
|
||||
# v4.0.2
|
||||
|
||||
## Changed
|
||||
|
||||
- Improved documentation of Verify() to note that JSONWebKeySet is a supported
|
||||
argument type (#104)
|
||||
- Defined exported error values for missing x5c header and unsupported elliptic
|
||||
curves error cases (#117)
|
||||
|
||||
# v4.0.1
|
||||
|
||||
## Fixed
|
||||
|
||||
-6
@@ -7,9 +7,3 @@ When submitting code, please make every effort to follow existing conventions
|
||||
and style in order to keep the code as readable as possible. Please also make
|
||||
sure all tests pass by running `go test`, and format your code with `go fmt`.
|
||||
We also recommend using `golint` and `errcheck`.
|
||||
|
||||
Before your code can be accepted into the project you must also sign the
|
||||
Individual Contributor License Agreement. We use [cla-assistant.io][1] and you
|
||||
will be prompted to sign once a pull request is opened.
|
||||
|
||||
[1]: https://cla-assistant.io/
|
||||
|
||||
+1
-9
@@ -9,14 +9,6 @@ Package jose aims to provide an implementation of the Javascript Object Signing
|
||||
and Encryption set of standards. This includes support for JSON Web Encryption,
|
||||
JSON Web Signature, and JSON Web Token standards.
|
||||
|
||||
**Disclaimer**: This library contains encryption software that is subject to
|
||||
the U.S. Export Administration Regulations. You may not export, re-export,
|
||||
transfer or download this code or any part of it in violation of any United
|
||||
States law, directive or regulation. In particular this software may not be
|
||||
exported or re-exported in any form or on any media to Iran, North Sudan,
|
||||
Syria, Cuba, or North Korea, or to denied persons or entities mentioned on any
|
||||
US maintained blocked list.
|
||||
|
||||
## Overview
|
||||
|
||||
The implementation follows the
|
||||
@@ -109,6 +101,6 @@ allows attaching a key id.
|
||||
|
||||
Examples can be found in the Godoc
|
||||
reference for this package. The
|
||||
[`jose-util`](https://github.com/go-jose/go-jose/tree/v4/jose-util)
|
||||
[`jose-util`](https://github.com/go-jose/go-jose/tree/main/jose-util)
|
||||
subdirectory also contains a small command-line utility which might be useful
|
||||
as an example as well.
|
||||
|
||||
+8
-2
@@ -459,7 +459,10 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
|
||||
return nil, fmt.Errorf("go-jose/go-jose: unsupported crit header")
|
||||
}
|
||||
|
||||
key := tryJWKS(decryptionKey, obj.Header)
|
||||
key, err := tryJWKS(decryptionKey, obj.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decrypter, err := newDecrypter(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -529,7 +532,10 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
|
||||
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported crit header")
|
||||
}
|
||||
|
||||
key := tryJWKS(decryptionKey, obj.Header)
|
||||
key, err := tryJWKS(decryptionKey, obj.Header)
|
||||
if err != nil {
|
||||
return -1, Header{}, nil, err
|
||||
}
|
||||
decrypter, err := newDecrypter(key)
|
||||
if err != nil {
|
||||
return -1, Header{}, nil, err
|
||||
|
||||
+3
-2
@@ -288,10 +288,11 @@ func ParseEncryptedCompact(
|
||||
keyAlgorithms []KeyAlgorithm,
|
||||
contentEncryption []ContentEncryption,
|
||||
) (*JSONWebEncryption, error) {
|
||||
parts := strings.Split(input, ".")
|
||||
if len(parts) != 5 {
|
||||
// Five parts is four separators
|
||||
if strings.Count(input, ".") != 4 {
|
||||
return nil, fmt.Errorf("go-jose/go-jose: compact JWE format must have five parts")
|
||||
}
|
||||
parts := strings.SplitN(input, ".", 5)
|
||||
|
||||
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||||
if err != nil {
|
||||
|
||||
+18
-7
@@ -239,10 +239,10 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
||||
keyPub = key
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("go-jose/go-jose: unknown curve %s'", raw.Crv)
|
||||
return fmt.Errorf("go-jose/go-jose: unknown curve %s'", raw.Crv)
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("go-jose/go-jose: unknown json web key type '%s'", raw.Kty)
|
||||
return fmt.Errorf("go-jose/go-jose: unknown json web key type '%s'", raw.Kty)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -779,7 +779,13 @@ func (key rawJSONWebKey) symmetricKey() ([]byte, error) {
|
||||
return key.K.bytes(), nil
|
||||
}
|
||||
|
||||
func tryJWKS(key interface{}, headers ...Header) interface{} {
|
||||
var (
|
||||
// ErrJWKSKidNotFound is returned when a JWKS does not contain a JWK with a
|
||||
// key ID which matches one in the provided tokens headers.
|
||||
ErrJWKSKidNotFound = errors.New("go-jose/go-jose: JWK with matching kid not found in JWK Set")
|
||||
)
|
||||
|
||||
func tryJWKS(key interface{}, headers ...Header) (interface{}, error) {
|
||||
var jwks JSONWebKeySet
|
||||
|
||||
switch jwksType := key.(type) {
|
||||
@@ -788,9 +794,11 @@ func tryJWKS(key interface{}, headers ...Header) interface{} {
|
||||
case JSONWebKeySet:
|
||||
jwks = jwksType
|
||||
default:
|
||||
return key
|
||||
// If the specified key is not a JWKS, return as is.
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// Determine the KID to search for from the headers.
|
||||
var kid string
|
||||
for _, header := range headers {
|
||||
if header.KeyID != "" {
|
||||
@@ -799,14 +807,17 @@ func tryJWKS(key interface{}, headers ...Header) interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
// If no KID is specified in the headers, reject.
|
||||
if kid == "" {
|
||||
return key
|
||||
return nil, ErrJWKSKidNotFound
|
||||
}
|
||||
|
||||
// Find the JWK with the matching KID. If no JWK with the specified KID is
|
||||
// found, reject.
|
||||
keys := jwks.Key(kid)
|
||||
if len(keys) == 0 {
|
||||
return key
|
||||
return nil, ErrJWKSKidNotFound
|
||||
}
|
||||
|
||||
return keys[0].Key
|
||||
return keys[0].Key, nil
|
||||
}
|
||||
|
||||
+49
-8
@@ -75,7 +75,14 @@ type Signature struct {
|
||||
original *rawSignatureInfo
|
||||
}
|
||||
|
||||
// ParseSigned parses a signed message in JWS Compact or JWS JSON Serialization.
|
||||
// ParseSigned parses a signed message in JWS Compact or JWS JSON Serialization. Validation fails if
|
||||
// the JWS is signed with an algorithm that isn't in the provided list of signature algorithms.
|
||||
// Applications should decide for themselves which signature algorithms are acceptable. If you're
|
||||
// not sure which signature algorithms your application might receive, consult the documentation of
|
||||
// the program which provides them or the protocol that you are implementing. You can also try
|
||||
// getting an example JWS and decoding it with a tool like https://jwt.io to see what its "alg"
|
||||
// header parameter indicates. The signature on the JWS does not get validated during parsing. Call
|
||||
// Verify() after parsing to validate the signature and obtain the payload.
|
||||
//
|
||||
// https://datatracker.ietf.org/doc/html/rfc7515#section-7
|
||||
func ParseSigned(
|
||||
@@ -90,7 +97,14 @@ func ParseSigned(
|
||||
return parseSignedCompact(signature, nil, signatureAlgorithms)
|
||||
}
|
||||
|
||||
// ParseSignedCompact parses a message in JWS Compact Serialization.
|
||||
// ParseSignedCompact parses a message in JWS Compact Serialization. Validation fails if the JWS is
|
||||
// signed with an algorithm that isn't in the provided list of signature algorithms. Applications
|
||||
// should decide for themselves which signature algorithms are acceptable.If you're not sure which
|
||||
// signature algorithms your application might receive, consult the documentation of the program
|
||||
// which provides them or the protocol that you are implementing. You can also try getting an
|
||||
// example JWS and decoding it with a tool like https://jwt.io to see what its "alg" header
|
||||
// parameter indicates. The signature on the JWS does not get validated during parsing. Call
|
||||
// Verify() after parsing to validate the signature and obtain the payload.
|
||||
//
|
||||
// https://datatracker.ietf.org/doc/html/rfc7515#section-7.1
|
||||
func ParseSignedCompact(
|
||||
@@ -101,6 +115,15 @@ func ParseSignedCompact(
|
||||
}
|
||||
|
||||
// ParseDetached parses a signed message in compact serialization format with detached payload.
|
||||
// Validation fails if the JWS is signed with an algorithm that isn't in the provided list of
|
||||
// signature algorithms. Applications should decide for themselves which signature algorithms are
|
||||
// acceptable. If you're not sure which signature algorithms your application might receive, consult
|
||||
// the documentation of the program which provides them or the protocol that you are implementing.
|
||||
// You can also try getting an example JWS and decoding it with a tool like https://jwt.io to see
|
||||
// what its "alg" header parameter indicates. The signature on the JWS does not get validated during
|
||||
// parsing. Call Verify() after parsing to validate the signature and obtain the payload.
|
||||
//
|
||||
// https://datatracker.ietf.org/doc/html/rfc7515#appendix-F
|
||||
func ParseDetached(
|
||||
signature string,
|
||||
payload []byte,
|
||||
@@ -181,6 +204,25 @@ func containsSignatureAlgorithm(haystack []SignatureAlgorithm, needle SignatureA
|
||||
return false
|
||||
}
|
||||
|
||||
// ErrUnexpectedSignatureAlgorithm is returned when the signature algorithm in
|
||||
// the JWS header does not match one of the expected algorithms.
|
||||
type ErrUnexpectedSignatureAlgorithm struct {
|
||||
// Got is the signature algorithm found in the JWS header.
|
||||
Got SignatureAlgorithm
|
||||
expected []SignatureAlgorithm
|
||||
}
|
||||
|
||||
func (e *ErrUnexpectedSignatureAlgorithm) Error() string {
|
||||
return fmt.Sprintf("unexpected signature algorithm %q; expected %q", e.Got, e.expected)
|
||||
}
|
||||
|
||||
func newErrUnexpectedSignatureAlgorithm(got SignatureAlgorithm, expected []SignatureAlgorithm) error {
|
||||
return &ErrUnexpectedSignatureAlgorithm{
|
||||
Got: got,
|
||||
expected: expected,
|
||||
}
|
||||
}
|
||||
|
||||
// sanitized produces a cleaned-up JWS object from the raw JSON.
|
||||
func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgorithm) (*JSONWebSignature, error) {
|
||||
if len(signatureAlgorithms) == 0 {
|
||||
@@ -236,8 +278,7 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
|
||||
|
||||
alg := SignatureAlgorithm(signature.Header.Algorithm)
|
||||
if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
|
||||
return nil, fmt.Errorf("go-jose/go-jose: unexpected signature algorithm %q; expected %q",
|
||||
alg, signatureAlgorithms)
|
||||
return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms)
|
||||
}
|
||||
|
||||
if signature.header != nil {
|
||||
@@ -285,8 +326,7 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
|
||||
|
||||
alg := SignatureAlgorithm(obj.Signatures[i].Header.Algorithm)
|
||||
if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
|
||||
return nil, fmt.Errorf("go-jose/go-jose: unexpected signature algorithm %q; expected %q",
|
||||
alg, signatureAlgorithms)
|
||||
return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms)
|
||||
}
|
||||
|
||||
if obj.Signatures[i].header != nil {
|
||||
@@ -327,10 +367,11 @@ func parseSignedCompact(
|
||||
payload []byte,
|
||||
signatureAlgorithms []SignatureAlgorithm,
|
||||
) (*JSONWebSignature, error) {
|
||||
parts := strings.Split(input, ".")
|
||||
if len(parts) != 3 {
|
||||
// Three parts is two separators
|
||||
if strings.Count(input, ".") != 2 {
|
||||
return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts")
|
||||
}
|
||||
parts := strings.SplitN(input, ".", 3)
|
||||
|
||||
if parts[1] != "" && payload != nil {
|
||||
return nil, fmt.Errorf("go-jose/go-jose: payload is not detached")
|
||||
|
||||
+3
@@ -83,6 +83,9 @@ func (o *opaqueVerifier) verifyPayload(payload []byte, signature []byte, alg Sig
|
||||
}
|
||||
|
||||
// OpaqueKeyEncrypter is an interface that supports encrypting keys with an opaque key.
|
||||
//
|
||||
// Note: this cannot currently be implemented outside this package because of its
|
||||
// unexported method.
|
||||
type OpaqueKeyEncrypter interface {
|
||||
// KeyID returns the kid
|
||||
KeyID() string
|
||||
|
||||
+8
-2
@@ -71,6 +71,12 @@ var (
|
||||
// ErrUnprotectedNonce indicates that while parsing a JWS or JWE object, a
|
||||
// nonce header parameter was included in an unprotected header object.
|
||||
ErrUnprotectedNonce = errors.New("go-jose/go-jose: Nonce parameter included in unprotected header")
|
||||
|
||||
// ErrMissingX5cHeader indicates that the JWT header is missing x5c headers.
|
||||
ErrMissingX5cHeader = errors.New("go-jose/go-jose: no x5c header present in message")
|
||||
|
||||
// ErrUnsupportedEllipticCurve indicates unsupported or unknown elliptic curve has been found.
|
||||
ErrUnsupportedEllipticCurve = errors.New("go-jose/go-jose: unsupported/unknown elliptic curve")
|
||||
)
|
||||
|
||||
// Key management algorithms
|
||||
@@ -199,7 +205,7 @@ type Header struct {
|
||||
// not be validated with the given verify options.
|
||||
func (h Header) Certificates(opts x509.VerifyOptions) ([][]*x509.Certificate, error) {
|
||||
if len(h.certificates) == 0 {
|
||||
return nil, errors.New("go-jose/go-jose: no x5c header present in message")
|
||||
return nil, ErrMissingX5cHeader
|
||||
}
|
||||
|
||||
leaf := h.certificates[0]
|
||||
@@ -501,7 +507,7 @@ func curveName(crv elliptic.Curve) (string, error) {
|
||||
case elliptic.P521():
|
||||
return "P-521", nil
|
||||
default:
|
||||
return "", fmt.Errorf("go-jose/go-jose: unsupported/unknown elliptic curve")
|
||||
return "", ErrUnsupportedEllipticCurve
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-2
@@ -358,6 +358,8 @@ func (ctx *genericSigner) Options() SignerOptions {
|
||||
// - *rsa.PublicKey
|
||||
// - *JSONWebKey
|
||||
// - JSONWebKey
|
||||
// - *JSONWebKeySet
|
||||
// - JSONWebKeySet
|
||||
// - []byte (an HMAC key)
|
||||
// - Any type that implements the OpaqueVerifier interface.
|
||||
//
|
||||
@@ -388,7 +390,10 @@ func (obj JSONWebSignature) UnsafePayloadWithoutVerification() []byte {
|
||||
// The verificationKey argument must have one of the types allowed for the
|
||||
// verificationKey argument of JSONWebSignature.Verify().
|
||||
func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey interface{}) error {
|
||||
key := tryJWKS(verificationKey, obj.headers()...)
|
||||
key, err := tryJWKS(verificationKey, obj.headers()...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verifier, err := newVerifier(key)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -453,7 +458,10 @@ func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signa
|
||||
// The verificationKey argument must have one of the types allowed for the
|
||||
// verificationKey argument of JSONWebSignature.Verify().
|
||||
func (obj JSONWebSignature) DetachedVerifyMulti(payload []byte, verificationKey interface{}) (int, Signature, error) {
|
||||
key := tryJWKS(verificationKey, obj.headers()...)
|
||||
key, err := tryJWKS(verificationKey, obj.headers()...)
|
||||
if err != nil {
|
||||
return -1, Signature{}, err
|
||||
}
|
||||
verifier, err := newVerifier(key)
|
||||
if err != nil {
|
||||
return -1, Signature{}, err
|
||||
|
||||
+9
-4
@@ -21,6 +21,7 @@ import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/pbkdf2"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
@@ -30,8 +31,6 @@ import (
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
|
||||
josecipher "github.com/go-jose/go-jose/v4/cipher"
|
||||
)
|
||||
|
||||
@@ -330,7 +329,10 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
|
||||
|
||||
// derive key
|
||||
keyLen, h := getPbkdf2Params(alg)
|
||||
key := pbkdf2.Key(ctx.key, salt, ctx.p2c, keyLen, h)
|
||||
key, err := pbkdf2.Key(h, string(ctx.key), salt, ctx.p2c, keyLen)
|
||||
if err != nil {
|
||||
return recipientInfo{}, nil
|
||||
}
|
||||
|
||||
// use AES cipher with derived key
|
||||
block, err := aes.NewCipher(key)
|
||||
@@ -432,7 +434,10 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
|
||||
|
||||
// derive key
|
||||
keyLen, h := getPbkdf2Params(alg)
|
||||
key := pbkdf2.Key(ctx.key, salt, p2c, keyLen, h)
|
||||
key, err := pbkdf2.Key(h, string(ctx.key), salt, p2c, keyLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// use AES cipher with derived key
|
||||
block, err := aes.NewCipher(key)
|
||||
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
# A minimal logging API for Go
|
||||
|
||||
[](https://pkg.go.dev/github.com/go-logr/logr)
|
||||
[](https://goreportcard.com/report/github.com/go-logr/logr)
|
||||
[](https://securityscorecards.dev/viewer/?platform=github.com&org=go-logr&repo=logr)
|
||||
|
||||
logr offers an(other) opinion on how Go programs and libraries can do logging
|
||||
|
||||
+94
-91
@@ -236,15 +236,14 @@ func newFormatter(opts Options, outfmt outputFormat) Formatter {
|
||||
// implementation. It should be constructed with NewFormatter. Some of
|
||||
// its methods directly implement logr.LogSink.
|
||||
type Formatter struct {
|
||||
outputFormat outputFormat
|
||||
prefix string
|
||||
values []any
|
||||
valuesStr string
|
||||
parentValuesStr string
|
||||
depth int
|
||||
opts *Options
|
||||
group string // for slog groups
|
||||
groupDepth int
|
||||
outputFormat outputFormat
|
||||
prefix string
|
||||
values []any
|
||||
valuesStr string
|
||||
depth int
|
||||
opts *Options
|
||||
groupName string // for slog groups
|
||||
groups []groupDef
|
||||
}
|
||||
|
||||
// outputFormat indicates which outputFormat to use.
|
||||
@@ -257,6 +256,13 @@ const (
|
||||
outputJSON
|
||||
)
|
||||
|
||||
// groupDef represents a saved group. The values may be empty, but we don't
|
||||
// know if we need to render the group until the final record is rendered.
|
||||
type groupDef struct {
|
||||
name string
|
||||
values string
|
||||
}
|
||||
|
||||
// PseudoStruct is a list of key-value pairs that gets logged as a struct.
|
||||
type PseudoStruct []any
|
||||
|
||||
@@ -264,76 +270,102 @@ type PseudoStruct []any
|
||||
func (f Formatter) render(builtins, args []any) string {
|
||||
// Empirically bytes.Buffer is faster than strings.Builder for this.
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
|
||||
if f.outputFormat == outputJSON {
|
||||
buf.WriteByte('{') // for the whole line
|
||||
buf.WriteByte('{') // for the whole record
|
||||
}
|
||||
|
||||
// Render builtins
|
||||
vals := builtins
|
||||
if hook := f.opts.RenderBuiltinsHook; hook != nil {
|
||||
vals = hook(f.sanitize(vals))
|
||||
}
|
||||
f.flatten(buf, vals, false, false) // keys are ours, no need to escape
|
||||
f.flatten(buf, vals, false) // keys are ours, no need to escape
|
||||
continuing := len(builtins) > 0
|
||||
|
||||
if f.parentValuesStr != "" {
|
||||
// Turn the inner-most group into a string
|
||||
argsStr := func() string {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
|
||||
vals = args
|
||||
if hook := f.opts.RenderArgsHook; hook != nil {
|
||||
vals = hook(f.sanitize(vals))
|
||||
}
|
||||
f.flatten(buf, vals, true) // escape user-provided keys
|
||||
|
||||
return buf.String()
|
||||
}()
|
||||
|
||||
// Render the stack of groups from the inside out.
|
||||
bodyStr := f.renderGroup(f.groupName, f.valuesStr, argsStr)
|
||||
for i := len(f.groups) - 1; i >= 0; i-- {
|
||||
grp := &f.groups[i]
|
||||
if grp.values == "" && bodyStr == "" {
|
||||
// no contents, so we must elide the whole group
|
||||
continue
|
||||
}
|
||||
bodyStr = f.renderGroup(grp.name, grp.values, bodyStr)
|
||||
}
|
||||
|
||||
if bodyStr != "" {
|
||||
if continuing {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
buf.WriteString(f.parentValuesStr)
|
||||
continuing = true
|
||||
}
|
||||
|
||||
groupDepth := f.groupDepth
|
||||
if f.group != "" {
|
||||
if f.valuesStr != "" || len(args) != 0 {
|
||||
if continuing {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteByte('{') // for the group
|
||||
continuing = false
|
||||
} else {
|
||||
// The group was empty
|
||||
groupDepth--
|
||||
}
|
||||
}
|
||||
|
||||
if f.valuesStr != "" {
|
||||
if continuing {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
buf.WriteString(f.valuesStr)
|
||||
continuing = true
|
||||
}
|
||||
|
||||
vals = args
|
||||
if hook := f.opts.RenderArgsHook; hook != nil {
|
||||
vals = hook(f.sanitize(vals))
|
||||
}
|
||||
f.flatten(buf, vals, continuing, true) // escape user-provided keys
|
||||
|
||||
for i := 0; i < groupDepth; i++ {
|
||||
buf.WriteByte('}') // for the groups
|
||||
buf.WriteString(bodyStr)
|
||||
}
|
||||
|
||||
if f.outputFormat == outputJSON {
|
||||
buf.WriteByte('}') // for the whole line
|
||||
buf.WriteByte('}') // for the whole record
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// flatten renders a list of key-value pairs into a buffer. If continuing is
|
||||
// true, it assumes that the buffer has previous values and will emit a
|
||||
// separator (which depends on the output format) before the first pair it
|
||||
// writes. If escapeKeys is true, the keys are assumed to have
|
||||
// non-JSON-compatible characters in them and must be evaluated for escapes.
|
||||
// renderGroup returns a string representation of the named group with rendered
|
||||
// values and args. If the name is empty, this will return the values and args,
|
||||
// joined. If the name is not empty, this will return a single key-value pair,
|
||||
// where the value is a grouping of the values and args. If the values and
|
||||
// args are both empty, this will return an empty string, even if the name was
|
||||
// specified.
|
||||
func (f Formatter) renderGroup(name string, values string, args string) string {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
|
||||
needClosingBrace := false
|
||||
if name != "" && (values != "" || args != "") {
|
||||
buf.WriteString(f.quoted(name, true)) // escape user-provided keys
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteByte('{')
|
||||
needClosingBrace = true
|
||||
}
|
||||
|
||||
continuing := false
|
||||
if values != "" {
|
||||
buf.WriteString(values)
|
||||
continuing = true
|
||||
}
|
||||
|
||||
if args != "" {
|
||||
if continuing {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
buf.WriteString(args)
|
||||
}
|
||||
|
||||
if needClosingBrace {
|
||||
buf.WriteByte('}')
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// flatten renders a list of key-value pairs into a buffer. If escapeKeys is
|
||||
// true, the keys are assumed to have non-JSON-compatible characters in them
|
||||
// and must be evaluated for escapes.
|
||||
//
|
||||
// This function returns a potentially modified version of kvList, which
|
||||
// ensures that there is a value for every key (adding a value if needed) and
|
||||
// that each key is a string (substituting a key if needed).
|
||||
func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, escapeKeys bool) []any {
|
||||
func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, escapeKeys bool) []any {
|
||||
// This logic overlaps with sanitize() but saves one type-cast per key,
|
||||
// which can be measurable.
|
||||
if len(kvList)%2 != 0 {
|
||||
@@ -354,7 +386,7 @@ func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, esc
|
||||
}
|
||||
v := kvList[i+1]
|
||||
|
||||
if i > 0 || continuing {
|
||||
if i > 0 {
|
||||
if f.outputFormat == outputJSON {
|
||||
buf.WriteByte(f.comma())
|
||||
} else {
|
||||
@@ -766,46 +798,17 @@ func (f Formatter) sanitize(kvList []any) []any {
|
||||
// startGroup opens a new group scope (basically a sub-struct), which locks all
|
||||
// the current saved values and starts them anew. This is needed to satisfy
|
||||
// slog.
|
||||
func (f *Formatter) startGroup(group string) {
|
||||
func (f *Formatter) startGroup(name string) {
|
||||
// Unnamed groups are just inlined.
|
||||
if group == "" {
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Any saved values can no longer be changed.
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
continuing := false
|
||||
|
||||
if f.parentValuesStr != "" {
|
||||
buf.WriteString(f.parentValuesStr)
|
||||
continuing = true
|
||||
}
|
||||
|
||||
if f.group != "" && f.valuesStr != "" {
|
||||
if continuing {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteByte('{') // for the group
|
||||
continuing = false
|
||||
}
|
||||
|
||||
if f.valuesStr != "" {
|
||||
if continuing {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
buf.WriteString(f.valuesStr)
|
||||
}
|
||||
|
||||
// NOTE: We don't close the scope here - that's done later, when a log line
|
||||
// is actually rendered (because we have N scopes to close).
|
||||
|
||||
f.parentValuesStr = buf.String()
|
||||
n := len(f.groups)
|
||||
f.groups = append(f.groups[:n:n], groupDef{f.groupName, f.valuesStr})
|
||||
|
||||
// Start collecting new values.
|
||||
f.group = group
|
||||
f.groupDepth++
|
||||
f.groupName = name
|
||||
f.valuesStr = ""
|
||||
f.values = nil
|
||||
}
|
||||
@@ -900,7 +903,7 @@ func (f *Formatter) AddValues(kvList []any) {
|
||||
|
||||
// Pre-render values, so we don't have to do it on each Info/Error call.
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
f.flatten(buf, vals, false, true) // escape user-provided keys
|
||||
f.flatten(buf, vals, true) // escape user-provided keys
|
||||
f.valuesStr = buf.String()
|
||||
}
|
||||
|
||||
|
||||
Generated
Vendored
Generated
Vendored
+19
@@ -1,5 +1,24 @@
|
||||
# Changelog
|
||||
|
||||
## Release 3.2.3 (2022-11-29)
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated docs (thanks @book987 @aJetHorn @neelayu @pellizzetti @apricote @SaigyoujiYuyuko233 @AlekSi)
|
||||
- #348: Updated huandu/xstrings which fixed a snake case bug (thanks @yxxhero)
|
||||
- #353: Updated masterminds/semver which included bug fixes
|
||||
- #354: Updated golang.org/x/crypto which included bug fixes
|
||||
|
||||
## Release 3.2.2 (2021-02-04)
|
||||
|
||||
This is a re-release of 3.2.1 to satisfy something with the Go module system.
|
||||
|
||||
## Release 3.2.1 (2021-02-04)
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgraded `Masterminds/goutils` to `v1.1.1`. see the [Security Advisory](https://github.com/Masterminds/goutils/security/advisories/GHSA-xg2h-wx96-xgxr)
|
||||
|
||||
## Release 3.2.0 (2020-12-14)
|
||||
|
||||
### Added
|
||||
Generated
Vendored
Generated
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
# Slim-Sprig: Template functions for Go templates [](https://godoc.org/github.com/go-task/slim-sprig) [](https://goreportcard.com/report/github.com/go-task/slim-sprig)
|
||||
# Slim-Sprig: Template functions for Go templates [](https://pkg.go.dev/github.com/go-task/slim-sprig/v3)
|
||||
|
||||
Slim-Sprig is a fork of [Sprig](https://github.com/Masterminds/sprig), but with
|
||||
all functions that depend on external (non standard library) or crypto packages
|
||||
Generated
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
# https://taskfile.dev
|
||||
|
||||
version: '2'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
+8
@@ -122,6 +122,7 @@ func (p *Profile) preEncode() {
|
||||
}
|
||||
|
||||
p.defaultSampleTypeX = addString(strings, p.DefaultSampleType)
|
||||
p.docURLX = addString(strings, p.DocURL)
|
||||
|
||||
p.stringTable = make([]string, len(strings))
|
||||
for s, i := range strings {
|
||||
@@ -156,6 +157,7 @@ func (p *Profile) encode(b *buffer) {
|
||||
encodeInt64Opt(b, 12, p.Period)
|
||||
encodeInt64s(b, 13, p.commentX)
|
||||
encodeInt64(b, 14, p.defaultSampleTypeX)
|
||||
encodeInt64Opt(b, 15, p.docURLX)
|
||||
}
|
||||
|
||||
var profileDecoder = []decoder{
|
||||
@@ -237,6 +239,8 @@ var profileDecoder = []decoder{
|
||||
func(b *buffer, m message) error { return decodeInt64s(b, &m.(*Profile).commentX) },
|
||||
// int64 defaultSampleType = 14
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).defaultSampleTypeX) },
|
||||
// string doc_link = 15;
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).docURLX) },
|
||||
}
|
||||
|
||||
// postDecode takes the unexported fields populated by decode (with
|
||||
@@ -384,6 +388,7 @@ func (p *Profile) postDecode() error {
|
||||
|
||||
p.commentX = nil
|
||||
p.DefaultSampleType, err = getString(p.stringTable, &p.defaultSampleTypeX, err)
|
||||
p.DocURL, err = getString(p.stringTable, &p.docURLX, err)
|
||||
p.stringTable = nil
|
||||
return err
|
||||
}
|
||||
@@ -530,6 +535,7 @@ func (p *Line) decoder() []decoder {
|
||||
func (p *Line) encode(b *buffer) {
|
||||
encodeUint64Opt(b, 1, p.functionIDX)
|
||||
encodeInt64Opt(b, 2, p.Line)
|
||||
encodeInt64Opt(b, 3, p.Column)
|
||||
}
|
||||
|
||||
var lineDecoder = []decoder{
|
||||
@@ -538,6 +544,8 @@ var lineDecoder = []decoder{
|
||||
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Line).functionIDX) },
|
||||
// optional int64 line = 2
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Line) },
|
||||
// optional int64 column = 3
|
||||
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Column) },
|
||||
}
|
||||
|
||||
func (p *Function) decoder() []decoder {
|
||||
|
||||
+2
-2
@@ -56,7 +56,7 @@ func javaCPUProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte
|
||||
}
|
||||
|
||||
// Strip out addresses for better merge.
|
||||
if err = p.Aggregate(true, true, true, true, false); err != nil {
|
||||
if err = p.Aggregate(true, true, true, true, false, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func parseJavaProfile(b []byte) (*Profile, error) {
|
||||
}
|
||||
|
||||
// Strip out addresses for better merge.
|
||||
if err = p.Aggregate(true, true, true, true, false); err != nil {
|
||||
if err = p.Aggregate(true, true, true, true, false, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -326,12 +326,13 @@ func (l *Location) key() locationKey {
|
||||
key.addr -= l.Mapping.Start
|
||||
key.mappingID = l.Mapping.ID
|
||||
}
|
||||
lines := make([]string, len(l.Line)*2)
|
||||
lines := make([]string, len(l.Line)*3)
|
||||
for i, line := range l.Line {
|
||||
if line.Function != nil {
|
||||
lines[i*2] = strconv.FormatUint(line.Function.ID, 16)
|
||||
}
|
||||
lines[i*2+1] = strconv.FormatInt(line.Line, 16)
|
||||
lines[i*2+2] = strconv.FormatInt(line.Column, 16)
|
||||
}
|
||||
key.lines = strings.Join(lines, "|")
|
||||
return key
|
||||
@@ -418,6 +419,7 @@ func (pm *profileMerger) mapLine(src Line) Line {
|
||||
ln := Line{
|
||||
Function: pm.mapFunction(src.Function),
|
||||
Line: src.Line,
|
||||
Column: src.Column,
|
||||
}
|
||||
return ln
|
||||
}
|
||||
@@ -474,6 +476,7 @@ func combineHeaders(srcs []*Profile) (*Profile, error) {
|
||||
var timeNanos, durationNanos, period int64
|
||||
var comments []string
|
||||
seenComments := map[string]bool{}
|
||||
var docURL string
|
||||
var defaultSampleType string
|
||||
for _, s := range srcs {
|
||||
if timeNanos == 0 || s.TimeNanos < timeNanos {
|
||||
@@ -492,6 +495,9 @@ func combineHeaders(srcs []*Profile) (*Profile, error) {
|
||||
if defaultSampleType == "" {
|
||||
defaultSampleType = s.DefaultSampleType
|
||||
}
|
||||
if docURL == "" {
|
||||
docURL = s.DocURL
|
||||
}
|
||||
}
|
||||
|
||||
p := &Profile{
|
||||
@@ -507,6 +513,7 @@ func combineHeaders(srcs []*Profile) (*Profile, error) {
|
||||
|
||||
Comments: comments,
|
||||
DefaultSampleType: defaultSampleType,
|
||||
DocURL: docURL,
|
||||
}
|
||||
copy(p.SampleType, srcs[0].SampleType)
|
||||
return p, nil
|
||||
|
||||
+18
-5
@@ -39,6 +39,7 @@ type Profile struct {
|
||||
Location []*Location
|
||||
Function []*Function
|
||||
Comments []string
|
||||
DocURL string
|
||||
|
||||
DropFrames string
|
||||
KeepFrames string
|
||||
@@ -53,6 +54,7 @@ type Profile struct {
|
||||
encodeMu sync.Mutex
|
||||
|
||||
commentX []int64
|
||||
docURLX int64
|
||||
dropFramesX int64
|
||||
keepFramesX int64
|
||||
stringTable []string
|
||||
@@ -145,6 +147,7 @@ type Location struct {
|
||||
type Line struct {
|
||||
Function *Function
|
||||
Line int64
|
||||
Column int64
|
||||
|
||||
functionIDX uint64
|
||||
}
|
||||
@@ -436,7 +439,7 @@ func (p *Profile) CheckValid() error {
|
||||
// Aggregate merges the locations in the profile into equivalence
|
||||
// classes preserving the request attributes. It also updates the
|
||||
// samples to point to the merged locations.
|
||||
func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
|
||||
func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, columnnumber, address bool) error {
|
||||
for _, m := range p.Mapping {
|
||||
m.HasInlineFrames = m.HasInlineFrames && inlineFrame
|
||||
m.HasFunctions = m.HasFunctions && function
|
||||
@@ -458,7 +461,7 @@ func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address
|
||||
}
|
||||
|
||||
// Aggregate locations
|
||||
if !inlineFrame || !address || !linenumber {
|
||||
if !inlineFrame || !address || !linenumber || !columnnumber {
|
||||
for _, l := range p.Location {
|
||||
if !inlineFrame && len(l.Line) > 1 {
|
||||
l.Line = l.Line[len(l.Line)-1:]
|
||||
@@ -466,6 +469,12 @@ func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address
|
||||
if !linenumber {
|
||||
for i := range l.Line {
|
||||
l.Line[i].Line = 0
|
||||
l.Line[i].Column = 0
|
||||
}
|
||||
}
|
||||
if !columnnumber {
|
||||
for i := range l.Line {
|
||||
l.Line[i].Column = 0
|
||||
}
|
||||
}
|
||||
if !address {
|
||||
@@ -548,6 +557,9 @@ func (p *Profile) String() string {
|
||||
for _, c := range p.Comments {
|
||||
ss = append(ss, "Comment: "+c)
|
||||
}
|
||||
if url := p.DocURL; url != "" {
|
||||
ss = append(ss, fmt.Sprintf("Doc: %s", url))
|
||||
}
|
||||
if pt := p.PeriodType; pt != nil {
|
||||
ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
|
||||
}
|
||||
@@ -627,10 +639,11 @@ func (l *Location) string() string {
|
||||
for li := range l.Line {
|
||||
lnStr := "??"
|
||||
if fn := l.Line[li].Function; fn != nil {
|
||||
lnStr = fmt.Sprintf("%s %s:%d s=%d",
|
||||
lnStr = fmt.Sprintf("%s %s:%d:%d s=%d",
|
||||
fn.Name,
|
||||
fn.Filename,
|
||||
l.Line[li].Line,
|
||||
l.Line[li].Column,
|
||||
fn.StartLine)
|
||||
if fn.Name != fn.SystemName {
|
||||
lnStr = lnStr + "(" + fn.SystemName + ")"
|
||||
@@ -836,10 +849,10 @@ func (p *Profile) HasFileLines() bool {
|
||||
|
||||
// Unsymbolizable returns true if a mapping points to a binary for which
|
||||
// locations can't be symbolized in principle, at least now. Examples are
|
||||
// "[vdso]", [vsyscall]" and some others, see the code.
|
||||
// "[vdso]", "[vsyscall]" and some others, see the code.
|
||||
func (m *Mapping) Unsymbolizable() bool {
|
||||
name := filepath.Base(m.File)
|
||||
return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/")
|
||||
return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/") || m.File == "//anon"
|
||||
}
|
||||
|
||||
// Copy makes a fully independent copy of a profile.
|
||||
|
||||
+10
-6
@@ -24,15 +24,15 @@ const (
|
||||
|
||||
var SingletonFormatter = New(ColorModeTerminal)
|
||||
|
||||
func F(format string, args ...interface{}) string {
|
||||
func F(format string, args ...any) string {
|
||||
return SingletonFormatter.F(format, args...)
|
||||
}
|
||||
|
||||
func Fi(indentation uint, format string, args ...interface{}) string {
|
||||
func Fi(indentation uint, format string, args ...any) string {
|
||||
return SingletonFormatter.Fi(indentation, format, args...)
|
||||
}
|
||||
|
||||
func Fiw(indentation uint, maxWidth uint, format string, args ...interface{}) string {
|
||||
func Fiw(indentation uint, maxWidth uint, format string, args ...any) string {
|
||||
return SingletonFormatter.Fiw(indentation, maxWidth, format, args...)
|
||||
}
|
||||
|
||||
@@ -82,6 +82,10 @@ func New(colorMode ColorMode) Formatter {
|
||||
return fmt.Sprintf("\x1b[38;5;%dm", colorCode)
|
||||
}
|
||||
|
||||
if _, noColor := os.LookupEnv("GINKGO_NO_COLOR"); noColor {
|
||||
colorMode = ColorModeNone
|
||||
}
|
||||
|
||||
f := Formatter{
|
||||
ColorMode: colorMode,
|
||||
colors: map[string]string{
|
||||
@@ -111,15 +115,15 @@ func New(colorMode ColorMode) Formatter {
|
||||
return f
|
||||
}
|
||||
|
||||
func (f Formatter) F(format string, args ...interface{}) string {
|
||||
func (f Formatter) F(format string, args ...any) string {
|
||||
return f.Fi(0, format, args...)
|
||||
}
|
||||
|
||||
func (f Formatter) Fi(indentation uint, format string, args ...interface{}) string {
|
||||
func (f Formatter) Fi(indentation uint, format string, args ...any) string {
|
||||
return f.Fiw(indentation, 0, format, args...)
|
||||
}
|
||||
|
||||
func (f Formatter) Fiw(indentation uint, maxWidth uint, format string, args ...interface{}) string {
|
||||
func (f Formatter) Fiw(indentation uint, maxWidth uint, format string, args ...any) string {
|
||||
out := f.style(format)
|
||||
if len(args) > 0 {
|
||||
out = fmt.Sprintf(out, args...)
|
||||
|
||||
+19
-2
@@ -2,6 +2,8 @@ package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/internal"
|
||||
@@ -42,7 +44,7 @@ func buildSpecs(args []string, cliConfig types.CLIConfig, goFlagsConfig types.Go
|
||||
internal.VerifyCLIAndFrameworkVersion(suites)
|
||||
|
||||
opc := internal.NewOrderedParallelCompiler(cliConfig.ComputedNumCompilers())
|
||||
opc.StartCompiling(suites, goFlagsConfig)
|
||||
opc.StartCompiling(suites, goFlagsConfig, true)
|
||||
|
||||
for {
|
||||
suiteIdx, suite := opc.Next()
|
||||
@@ -53,7 +55,22 @@ func buildSpecs(args []string, cliConfig types.CLIConfig, goFlagsConfig types.Go
|
||||
if suite.State.Is(internal.TestSuiteStateFailedToCompile) {
|
||||
fmt.Println(suite.CompilationError.Error())
|
||||
} else {
|
||||
fmt.Printf("Compiled %s.test\n", suite.PackageName)
|
||||
var testBinPath string
|
||||
if len(goFlagsConfig.O) != 0 {
|
||||
stat, err := os.Stat(goFlagsConfig.O)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if stat.IsDir() {
|
||||
testBinPath = goFlagsConfig.O + "/" + suite.PackageName + ".test"
|
||||
} else {
|
||||
testBinPath = goFlagsConfig.O
|
||||
}
|
||||
}
|
||||
if len(testBinPath) == 0 {
|
||||
testBinPath = path.Join(suite.Path, suite.PackageName+".test")
|
||||
}
|
||||
fmt.Printf("Compiled %s\n", testBinPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ func Abort(details AbortDetails) {
|
||||
panic(details)
|
||||
}
|
||||
|
||||
func AbortGracefullyWith(format string, args ...interface{}) {
|
||||
func AbortGracefullyWith(format string, args ...any) {
|
||||
Abort(AbortDetails{
|
||||
ExitCode: 0,
|
||||
Error: fmt.Errorf(format, args...),
|
||||
@@ -20,7 +20,7 @@ func AbortGracefullyWith(format string, args ...interface{}) {
|
||||
})
|
||||
}
|
||||
|
||||
func AbortWith(format string, args ...interface{}) {
|
||||
func AbortWith(format string, args ...any) {
|
||||
Abort(AbortDetails{
|
||||
ExitCode: 1,
|
||||
Error: fmt.Errorf(format, args...),
|
||||
@@ -28,7 +28,7 @@ func AbortWith(format string, args ...interface{}) {
|
||||
})
|
||||
}
|
||||
|
||||
func AbortWithUsage(format string, args ...interface{}) {
|
||||
func AbortWithUsage(format string, args ...any) {
|
||||
Abort(AbortDetails{
|
||||
ExitCode: 1,
|
||||
Error: fmt.Errorf(format, args...),
|
||||
|
||||
+5
-1
@@ -24,7 +24,11 @@ func (c Command) Run(args []string, additionalArgs []string) {
|
||||
if err != nil {
|
||||
AbortWithUsage(err.Error())
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
if len(arg) > 1 && strings.HasPrefix(arg, "-") {
|
||||
AbortWith(types.GinkgoErrors.FlagAfterPositionalParameter().Error())
|
||||
}
|
||||
}
|
||||
c.Command(args, additionalArgs)
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -68,7 +68,6 @@ func (p Program) RunAndExit(osArgs []string) {
|
||||
fmt.Fprintln(p.ErrWriter, deprecationTracker.DeprecationsReport())
|
||||
}
|
||||
p.Exiter(exitCode)
|
||||
return
|
||||
}()
|
||||
|
||||
args, additionalArgs := []string{}, []string{}
|
||||
@@ -157,7 +156,6 @@ func (p Program) handleHelpRequestsAndExit(writer io.Writer, args []string) {
|
||||
p.EmitUsage(writer)
|
||||
Abort(AbortDetails{ExitCode: 1})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p Program) EmitUsage(writer io.Writer) {
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import (
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
sprig "github.com/go-task/slim-sprig"
|
||||
sprig "github.com/go-task/slim-sprig/v3"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/internal"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
|
||||
+2
-1
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
sprig "github.com/go-task/slim-sprig"
|
||||
sprig "github.com/go-task/slim-sprig/v3"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/command"
|
||||
"github.com/onsi/ginkgo/v2/ginkgo/internal"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
@@ -174,6 +174,7 @@ func moduleName(modRoot string) string {
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer modFile.Close()
|
||||
|
||||
mod := make([]byte, 128)
|
||||
_, err = modFile.Read(mod)
|
||||
|
||||
+16
-4
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
)
|
||||
|
||||
func CompileSuite(suite TestSuite, goFlagsConfig types.GoFlagsConfig) TestSuite {
|
||||
func CompileSuite(suite TestSuite, goFlagsConfig types.GoFlagsConfig, preserveSymbols bool) TestSuite {
|
||||
if suite.PathToCompiledTest != "" {
|
||||
return suite
|
||||
}
|
||||
@@ -25,6 +25,18 @@ func CompileSuite(suite TestSuite, goFlagsConfig types.GoFlagsConfig) TestSuite
|
||||
return suite
|
||||
}
|
||||
|
||||
if len(goFlagsConfig.O) > 0 {
|
||||
userDefinedPath, err := filepath.Abs(goFlagsConfig.O)
|
||||
if err != nil {
|
||||
suite.State = TestSuiteStateFailedToCompile
|
||||
suite.CompilationError = fmt.Errorf("Failed to compute compilation target path %s:\n%s", goFlagsConfig.O, err.Error())
|
||||
return suite
|
||||
}
|
||||
path = userDefinedPath
|
||||
}
|
||||
|
||||
goFlagsConfig.O = path
|
||||
|
||||
ginkgoInvocationPath, _ := os.Getwd()
|
||||
ginkgoInvocationPath, _ = filepath.Abs(ginkgoInvocationPath)
|
||||
packagePath := suite.AbsPath()
|
||||
@@ -34,7 +46,7 @@ func CompileSuite(suite TestSuite, goFlagsConfig types.GoFlagsConfig) TestSuite
|
||||
suite.CompilationError = fmt.Errorf("Failed to get relative path from package to the current working directory:\n%s", err.Error())
|
||||
return suite
|
||||
}
|
||||
args, err := types.GenerateGoTestCompileArgs(goFlagsConfig, path, "./", pathToInvocationPath)
|
||||
args, err := types.GenerateGoTestCompileArgs(goFlagsConfig, "./", pathToInvocationPath, preserveSymbols)
|
||||
if err != nil {
|
||||
suite.State = TestSuiteStateFailedToCompile
|
||||
suite.CompilationError = fmt.Errorf("Failed to generate go test compile flags:\n%s", err.Error())
|
||||
@@ -108,7 +120,7 @@ func NewOrderedParallelCompiler(numCompilers int) *OrderedParallelCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
func (opc *OrderedParallelCompiler) StartCompiling(suites TestSuites, goFlagsConfig types.GoFlagsConfig) {
|
||||
func (opc *OrderedParallelCompiler) StartCompiling(suites TestSuites, goFlagsConfig types.GoFlagsConfig, preserveSymbols bool) {
|
||||
opc.stopped = false
|
||||
opc.idx = 0
|
||||
opc.numSuites = len(suites)
|
||||
@@ -123,7 +135,7 @@ func (opc *OrderedParallelCompiler) StartCompiling(suites TestSuites, goFlagsCon
|
||||
stopped := opc.stopped
|
||||
opc.mutex.Unlock()
|
||||
if !stopped {
|
||||
suite = CompileSuite(suite, goFlagsConfig)
|
||||
suite = CompileSuite(suite, goFlagsConfig, preserveSymbols)
|
||||
}
|
||||
c <- suite
|
||||
}
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) 2015, Wade Simmons
|
||||
// All rights reserved.
|
||||
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Package gocovmerge takes the results from multiple `go test -coverprofile`
|
||||
// runs and merges them into one profile
|
||||
|
||||
// this file was originally taken from the gocovmerge project
|
||||
// see also: https://go.shabbyrobe.org/gocovmerge
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/cover"
|
||||
)
|
||||
|
||||
func AddCoverProfile(profiles []*cover.Profile, p *cover.Profile) []*cover.Profile {
|
||||
i := sort.Search(len(profiles), func(i int) bool { return profiles[i].FileName >= p.FileName })
|
||||
if i < len(profiles) && profiles[i].FileName == p.FileName {
|
||||
MergeCoverProfiles(profiles[i], p)
|
||||
} else {
|
||||
profiles = append(profiles, nil)
|
||||
copy(profiles[i+1:], profiles[i:])
|
||||
profiles[i] = p
|
||||
}
|
||||
return profiles
|
||||
}
|
||||
|
||||
func DumpCoverProfiles(profiles []*cover.Profile, out io.Writer) error {
|
||||
if len(profiles) == 0 {
|
||||
return nil
|
||||
}
|
||||
if _, err := fmt.Fprintf(out, "mode: %s\n", profiles[0].Mode); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, p := range profiles {
|
||||
for _, b := range p.Blocks {
|
||||
if _, err := fmt.Fprintf(out, "%s:%d.%d,%d.%d %d %d\n", p.FileName, b.StartLine, b.StartCol, b.EndLine, b.EndCol, b.NumStmt, b.Count); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MergeCoverProfiles(into *cover.Profile, merge *cover.Profile) error {
|
||||
if into.Mode != merge.Mode {
|
||||
return fmt.Errorf("cannot merge profiles with different modes")
|
||||
}
|
||||
// Since the blocks are sorted, we can keep track of where the last block
|
||||
// was inserted and only look at the blocks after that as targets for merge
|
||||
startIndex := 0
|
||||
for _, b := range merge.Blocks {
|
||||
var err error
|
||||
startIndex, err = mergeProfileBlock(into, b, startIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeProfileBlock(p *cover.Profile, pb cover.ProfileBlock, startIndex int) (int, error) {
|
||||
sortFunc := func(i int) bool {
|
||||
pi := p.Blocks[i+startIndex]
|
||||
return pi.StartLine >= pb.StartLine && (pi.StartLine != pb.StartLine || pi.StartCol >= pb.StartCol)
|
||||
}
|
||||
|
||||
i := 0
|
||||
if !sortFunc(i) {
|
||||
i = sort.Search(len(p.Blocks)-startIndex, sortFunc)
|
||||
}
|
||||
|
||||
i += startIndex
|
||||
if i < len(p.Blocks) && p.Blocks[i].StartLine == pb.StartLine && p.Blocks[i].StartCol == pb.StartCol {
|
||||
if p.Blocks[i].EndLine != pb.EndLine || p.Blocks[i].EndCol != pb.EndCol {
|
||||
return i, fmt.Errorf("gocovmerge: overlapping merge %v %v %v", p.FileName, p.Blocks[i], pb)
|
||||
}
|
||||
switch p.Mode {
|
||||
case "set":
|
||||
p.Blocks[i].Count |= pb.Count
|
||||
case "count", "atomic":
|
||||
p.Blocks[i].Count += pb.Count
|
||||
default:
|
||||
return i, fmt.Errorf("gocovmerge: unsupported covermode '%s'", p.Mode)
|
||||
}
|
||||
|
||||
} else {
|
||||
if i > 0 {
|
||||
pa := p.Blocks[i-1]
|
||||
if pa.EndLine >= pb.EndLine && (pa.EndLine != pb.EndLine || pa.EndCol > pb.EndCol) {
|
||||
return i, fmt.Errorf("gocovmerge: overlap before %v %v %v", p.FileName, pa, pb)
|
||||
}
|
||||
}
|
||||
if i < len(p.Blocks)-1 {
|
||||
pa := p.Blocks[i+1]
|
||||
if pa.StartLine <= pb.StartLine && (pa.StartLine != pb.StartLine || pa.StartCol < pb.StartCol) {
|
||||
return i, fmt.Errorf("gocovmerge: overlap after %v %v %v", p.FileName, pa, pb)
|
||||
}
|
||||
}
|
||||
p.Blocks = append(p.Blocks, cover.ProfileBlock{})
|
||||
copy(p.Blocks[i+1:], p.Blocks[i:])
|
||||
p.Blocks[i] = pb
|
||||
}
|
||||
|
||||
return i + 1, nil
|
||||
}
|
||||
+18
-28
@@ -1,7 +1,6 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
"github.com/google/pprof/profile"
|
||||
"github.com/onsi/ginkgo/v2/reporters"
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
"golang.org/x/tools/cover"
|
||||
)
|
||||
|
||||
func AbsPathForGeneratedAsset(assetName string, suite TestSuite, cliConfig types.CLIConfig, process int) string {
|
||||
@@ -144,38 +144,27 @@ func FinalizeProfilesAndReportsForSuites(suites TestSuites, cliConfig types.CLIC
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
//loads each profile, combines them, deletes them, stores them in destination
|
||||
// loads each profile, merges them, deletes them, stores them in destination
|
||||
func MergeAndCleanupCoverProfiles(profiles []string, destination string) error {
|
||||
combined := &bytes.Buffer{}
|
||||
modeRegex := regexp.MustCompile(`^mode: .*\n`)
|
||||
for i, profile := range profiles {
|
||||
contents, err := os.ReadFile(profile)
|
||||
var merged []*cover.Profile
|
||||
for _, file := range profiles {
|
||||
parsedProfiles, err := cover.ParseProfiles(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to read coverage file %s:\n%s", profile, err.Error())
|
||||
return err
|
||||
}
|
||||
os.Remove(profile)
|
||||
|
||||
// remove the cover mode line from every file
|
||||
// except the first one
|
||||
if i > 0 {
|
||||
contents = modeRegex.ReplaceAll(contents, []byte{})
|
||||
}
|
||||
|
||||
_, err = combined.Write(contents)
|
||||
|
||||
// Add a newline to the end of every file if missing.
|
||||
if err == nil && len(contents) > 0 && contents[len(contents)-1] != '\n' {
|
||||
_, err = combined.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to append to coverprofile:\n%s", err.Error())
|
||||
os.Remove(file)
|
||||
for _, p := range parsedProfiles {
|
||||
merged = AddCoverProfile(merged, p)
|
||||
}
|
||||
}
|
||||
|
||||
err := os.WriteFile(destination, combined.Bytes(), 0666)
|
||||
dst, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create combined cover profile:\n%s", err.Error())
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
err = DumpCoverProfiles(merged, dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -184,7 +173,7 @@ func GetCoverageFromCoverProfile(profile string) (float64, error) {
|
||||
cmd := exec.Command("go", "tool", "cover", "-func", profile)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Could not process Coverprofile %s: %s", profile, err.Error())
|
||||
return 0, fmt.Errorf("Could not process Coverprofile %s: %s - %s", profile, err.Error(), string(output))
|
||||
}
|
||||
re := regexp.MustCompile(`total:\s*\(statements\)\s*(\d*\.\d*)\%`)
|
||||
matches := re.FindStringSubmatch(string(output))
|
||||
@@ -208,6 +197,7 @@ func MergeProfiles(profilePaths []string, destination string) error {
|
||||
return fmt.Errorf("Could not open profile: %s\n%s", profilePath, err.Error())
|
||||
}
|
||||
prof, err := profile.Parse(proFile)
|
||||
_ = proFile.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not parse profile: %s\n%s", profilePath, err.Error())
|
||||
}
|
||||
|
||||
+5
-4
@@ -7,6 +7,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/ginkgo/v2/types"
|
||||
@@ -192,7 +193,7 @@ func precompiledTestSuite(path string) (TestSuite, error) {
|
||||
return TestSuite{}, errors.New("this is not a .test binary")
|
||||
}
|
||||
|
||||
if filepath.Ext(path) == ".test" && info.Mode()&0111 == 0 {
|
||||
if filepath.Ext(path) == ".test" && runtime.GOOS != "windows" && info.Mode()&0111 == 0 {
|
||||
return TestSuite{}, errors.New("this is not executable")
|
||||
}
|
||||
|
||||
@@ -225,7 +226,7 @@ func suitesInDir(dir string, recurse bool) TestSuites {
|
||||
files, _ := os.ReadDir(dir)
|
||||
re := regexp.MustCompile(`^[^._].*_test\.go$`)
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && re.Match([]byte(file.Name())) {
|
||||
if !file.IsDir() && re.MatchString(file.Name()) {
|
||||
suite := TestSuite{
|
||||
Path: relPath(dir),
|
||||
PackageName: packageNameForSuite(dir),
|
||||
@@ -240,7 +241,7 @@ func suitesInDir(dir string, recurse bool) TestSuites {
|
||||
if recurse {
|
||||
re = regexp.MustCompile(`^[._]`)
|
||||
for _, file := range files {
|
||||
if file.IsDir() && !re.Match([]byte(file.Name())) {
|
||||
if file.IsDir() && !re.MatchString(file.Name()) {
|
||||
suites = append(suites, suitesInDir(dir+"/"+file.Name(), recurse)...)
|
||||
}
|
||||
}
|
||||
@@ -271,7 +272,7 @@ func filesHaveGinkgoSuite(dir string, files []os.DirEntry) bool {
|
||||
reGinkgo := regexp.MustCompile(`package ginkgo|\/ginkgo"|\/ginkgo\/v2"|\/ginkgo\/v2/dsl/`)
|
||||
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && reTestFile.Match([]byte(file.Name())) {
|
||||
if !file.IsDir() && reTestFile.MatchString(file.Name()) {
|
||||
contents, _ := os.ReadFile(dir + "/" + file.Name())
|
||||
if reGinkgo.Match(contents) {
|
||||
return true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user