Compare commits

...

14 Commits

Author SHA1 Message Date
João "Pisco" Fernandes f8d12c9d39 Release 2025.6.0 2025-06-11 10:51:37 +01:00
Luis Neto 96ce66bd30 TUN-9016: update go to 1.24
## Summary

Update several moving parts of cloudflared build system:

* use goboring 1.24.2 in cfsetup
* update linter and fix lint issues
* update packages namely **quic-go and net**
* install script for macos
* update docker files to use go 1.24.1
* remove usage of cloudflare-go
* pin golang linter

Closes TUN-9016
2025-06-06 09:05:49 +00:00
João "Pisco" Fernandes e144eac2af TUN-9171: Use is_default_network instead of is_default to create vnet's
## Summary
The is_default field in the request body of the POST /virtual_networks endpoint has been
deprecated and should no longer be used. Clients should use the `is_default_network` field
instead for setting the default virtual network.

Closes TUN-9171
2025-06-05 15:02:21 +00:00
Devin Carr a62d63d49d Release 2025.5.0 2025-05-15 08:49:52 -07:00
Devin Carr 3bf9217de5 TUN-9319: Add dynamic loading of features to connections via ConnectionOptionsSnapshot
Make sure to enforce snapshots of features and client information for each connection
so that the feature information can change in the background. This allows for new features
to only be applied to a connection if it completely disconnects and attempts a reconnect.

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

Closes TUN-9319
2025-05-14 20:11:05 +00:00
Devin Carr 02705c44b2 TUN-9322: Add metric for unsupported RPC commands for datagram v3
Additionally adds support for the connection index as a label for the
datagram v3 specific tunnel metrics.

Closes TUN-9322
2025-05-13 16:11:09 +00:00
Devin Carr ce27840573 TUN-9291: Remove dynamic reloading of features for datagram v3
During a refresh of the supported features via the DNS TXT record,
cloudflared would update the internal feature list, but would not
propagate this information to the edge during a new connection.

This meant that a situation could occur in which cloudflared would
think that the client's connection could support datagram V3, and
would setup that muxer locally, but would not propagate that information
to the edge during a register connection in the `ClientInfo` of the
`ConnectionOptions`. This meant that the edge still thought that the
client was setup to support datagram V2 and since the protocols are
not backwards compatible, the local muxer for datagram V3 would reject
the incoming RPC calls.

To address this, the feature list will be fetched only once during
client bootstrapping and will persist as-is until the client is restarted.
This helps reduce the complexity involved with different connections
having possibly different sets of features when connecting to the edge.
The features will now be tied to the client and never diverge across
connections.

Also, retires the use of `support_datagram_v3` in-favor of
`support_datagram_v3_1` to reduce the risk of reusing the feature key.
The `dv3` TXT feature key is also deprecated.

Closes TUN-9291
2025-05-07 23:21:08 +00:00
GoncaloGarcia 40dc601e9d Release 2025.4.2 2025-04-30 14:15:20 +01:00
João "Pisco" Fernandes e5578cb74e Release 2025.4.1 2025-04-30 13:10:45 +01:00
João "Pisco" Fernandes bb765e741d chore: Do not use gitlab merge request pipelines
## Summary
If we define pipelines to trigger on merge requests,
they will take precedence over branch pipelines,
which is currently the way our old pipelines are still
triggered. This means that we can have a merge request
with green pipelines, but actually the external pipelines failed.
Therefore, we need to only rely on branch pipelines,
to ensure that we don't ignore the results from
external pipelines.

More information here:
- https://forum.gitlab.com/t/merge-request-considering-merge-request-pipelines-instead-of-branch-pipelines/111248/2
- https://docs.gitlab.com/17.6/ci/jobs/job_rules/#run-jobs-only-in-specific-pipeline-types
2025-04-30 12:01:43 +00:00
João "Pisco" Fernandes 10081602a4 Release 2025.4.1 2025-04-30 11:09:14 +01:00
Gonçalo Garcia 236fcf56d6 DEVTOOLS-16383: Create GitlabCI pipeline to release Mac builds
Adds a new Gitlab CI pipeline that releases cloudflared Mac builds and replaces the Teamcity adhoc job.
This will build, sign and create a new Github release or add the artifacts to an existing release if the other jobs finish first.
2025-04-30 09:57:52 +00:00
João "Pisco" Fernandes 73a9980f38 TUN-9255: Improve flush on write conditions in http2 tunnel type to match what is done on the edge
## Summary
We have adapted our edge services to better know when they should flush on write. This is an important
feature to ensure response types like Server Side Events are not buffered, and instead are propagated to the eyeball
as soon as possible. This commit implements a similar logic for http2 tunnel protocol that we use in our edge
services. By adding the new events stream header for json `application/x-ndjson` and using the content-length
and transfer-encoding headers as well, following the RFC's:
- https://datatracker.ietf.org/doc/html/rfc7230#section-4.1
- https://datatracker.ietf.org/doc/html/rfc9112#section-6.1

Closes TUN-9255
2025-04-24 11:49:19 +00:00
Tom Lianza 86e8585563 SDLC-3727 - Adding FIPS status to backstage
## Summary
This is a documentation change to help make sure we have an accurate FIPS inventory: https://wiki.cfdata.org/display/ENG/RFC%3A+Scalable+approach+for+managing+FIPS+compliance

Closes SDLC-3727
2025-04-10 16:58:04 +00:00
611 changed files with 24343 additions and 21668 deletions
+130
View File
@@ -0,0 +1,130 @@
stages: [build, release]
default:
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.cfdata.org
# This before_script is injected into every job that runs on master meaning that if there is no tag the step
# will succeed but only write "No tag present - Skipping" to the console.
.check_tag:
before_script:
- |
# Check if there is a Git tag pointing to HEAD
echo "Tag found: $(git tag --points-at HEAD | grep .)"
if git tag --points-at HEAD | grep .; then
echo "Tag found: $(git tag --points-at HEAD | grep .)"
export "VERSION=$(git tag --points-at HEAD | grep .)"
else
echo "No tag present — skipping."
exit 0
fi
## A set of predefined rules to use on the different jobs
.default_rules:
# Rules to run the job only on the master branch
run_on_master:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: always
- when: never
# Rules to run the job only on branches that are not master. This is needed because for now
# we need to keep a similar behavior due to the integration with teamcity, which requires us
# to not trigger pipelines on tags and/or merge requests.
run_on_branch:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_PIPELINE_SOURCE != "merge_request_event" && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
when: always
- when: never
# -----------------------------------------------
# Stage 1: Build on every PR
# -----------------------------------------------
build_cloudflared_macos: &build
stage: build
rules:
- !reference [.default_rules, run_on_branch]
tags:
- "macstadium-${RUNNER_ARCH}"
parallel:
matrix:
- RUNNER_ARCH: [arm, intel]
artifacts:
paths:
- artifacts/*
script:
- '[ "${RUNNER_ARCH}" = "arm" ] && export TARGET_ARCH=arm64'
- '[ "${RUNNER_ARCH}" = "intel" ] && export TARGET_ARCH=amd64'
- ARCH=$(uname -m)
- echo ARCH=$ARCH - TARGET_ARCH=$TARGET_ARCH
- ./.teamcity/mac/install-go.sh
- BUILD_SCRIPT=.teamcity/mac/build.sh
- if [[ ! -x ${BUILD_SCRIPT} ]] ; then exit ; fi
- set -euo pipefail
- echo "Executing ${BUILD_SCRIPT}"
- exec ${BUILD_SCRIPT}
# -----------------------------------------------
# Stage 1: Build and sign only on releases
# -----------------------------------------------
build_and_sign_cloudflared_macos:
<<: *build
rules:
- !reference [.default_rules, run_on_master]
secrets:
APPLE_DEV_CA_CERT:
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/apple_dev_ca_cert_v2/data@kv
file: false
CFD_CODE_SIGN_CERT:
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_code_sign_cert_v2/data@kv
file: false
CFD_CODE_SIGN_KEY:
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_code_sign_key_v2/data@kv
file: false
CFD_CODE_SIGN_PASS:
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_code_sign_pass_v2/data@kv
file: false
CFD_INSTALLER_CERT:
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_installer_cert_v2/data@kv
file: false
CFD_INSTALLER_KEY:
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_installer_key_v2/data@kv
file: false
CFD_INSTALLER_PASS:
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_installer_pass_v2/data@kv
file: false
# -----------------------------------------------
# Stage 2: Release to Github after building and signing
# -----------------------------------------------
release_cloudflared_macos_to_github:
stage: release
image: docker-registry.cfdata.org/stash/tun/docker-images/cloudflared-ci/main:6-8616fe631b76-amd64@sha256:96f4fd05e66cec03e0864c1bcf09324c130d4728eef45ee994716da499183614
extends: .check_tag
dependencies:
- build_and_sign_cloudflared_macos
rules:
- !reference [.default_rules, run_on_master]
cache:
paths:
- .cache/pip
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
KV_NAMESPACE: 380e19aa04314648949b6ad841417ebe
KV_ACCOUNT: 5ab4e9dfbd435d24068829fda0077963
secrets:
KV_API_TOKEN:
vault: gitlab/cloudflare/tun/cloudflared/_dev/cfd_kv_api_token/data@kv
file: false
API_KEY:
vault: gitlab/cloudflare/tun/cloudflared/_dev/cfd_github_api_key/data@kv
file: false
script:
- python3 --version ; pip --version # For debugging
- python3 -m venv venv
- source venv/bin/activate
- pip install pynacl==1.4.0 pygithub==1.55
- echo $VERSION
- echo $TAG_EXISTS
- echo "Running release because tag exists."
- make macos-release
+1 -1
View File
@@ -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.
+46 -7
View File
@@ -49,7 +49,7 @@ import_certificate() {
echo -n -e ${CERTIFICATE_ENV_VAR} | base64 -D > ${CERTIFICATE_FILE_NAME}
# we set || true here and for every `security import invoke` because the "duplicate SecKeychainItemImport" error
# will cause set -e to exit 1. It is okay we do this because we deliberately handle this error in the lines below.
local out=$(security import ${CERTIFICATE_FILE_NAME} -A 2>&1) || true
local out=$(security import ${CERTIFICATE_FILE_NAME} -T /usr/bin/pkgbuild -A 2>&1) || true
local exitcode=$?
# delete the certificate from disk
rm -rf ${CERTIFICATE_FILE_NAME}
@@ -68,6 +68,28 @@ import_certificate() {
fi
}
create_cloudflared_build_keychain() {
# Reusing the private key password as the keychain key
local PRIVATE_KEY_PASS=$1
# Create keychain only if it doesn't already exist
if [ ! -f "$HOME/Library/Keychains/cloudflared_build_keychain.keychain-db" ]; then
security create-keychain -p "$PRIVATE_KEY_PASS" cloudflared_build_keychain
else
echo "Keychain already exists: cloudflared_build_keychain"
fi
# Append temp keychain to the user domain
security list-keychains -d user -s cloudflared_build_keychain $(security list-keychains -d user | sed s/\"//g)
# Remove relock timeout
security set-keychain-settings cloudflared_build_keychain
# Unlock keychain so it doesn't require password
security unlock-keychain -p "$PRIVATE_KEY_PASS" cloudflared_build_keychain
}
# Imports private keys to the Apple KeyChain
import_private_keys() {
local PRIVATE_KEY_NAME=$1
@@ -83,7 +105,7 @@ import_private_keys() {
echo -n -e ${PRIVATE_KEY_ENV_VAR} | base64 -D > ${PRIVATE_KEY_FILE_NAME}
# we set || true here and for every `security import invoke` because the "duplicate SecKeychainItemImport" error
# will cause set -e to exit 1. It is okay we do this because we deliberately handle this error in the lines below.
local out=$(security import ${PRIVATE_KEY_FILE_NAME} -A -P "${PRIVATE_KEY_PASS}" 2>&1) || true
local out=$(security import ${PRIVATE_KEY_FILE_NAME} -k cloudflared_build_keychain -P "$PRIVATE_KEY_PASS" -T /usr/bin/pkgbuild -A -P "${PRIVATE_KEY_PASS}" 2>&1) || true
local exitcode=$?
rm -rf ${PRIVATE_KEY_FILE_NAME}
if [ -n "$out" ]; then
@@ -100,6 +122,9 @@ import_private_keys() {
fi
}
# Create temp keychain only for this build
create_cloudflared_build_keychain "${CFD_CODE_SIGN_PASS}"
# Add Apple Root Developer certificate to the key chain
import_certificate "Apple Developer CA" "${APPLE_DEV_CA_CERT}" "${APPLE_CA_CERT}"
@@ -119,8 +144,8 @@ import_certificate "Developer ID Installer" "${CFD_INSTALLER_CERT}" "${INSTALLER
if [[ ! -z "$CFD_CODE_SIGN_NAME" ]]; then
CODE_SIGN_NAME="${CFD_CODE_SIGN_NAME}"
else
if [[ -n "$(security find-certificate -c "Developer ID Application" | cut -d'"' -f 4 -s | grep "Developer ID Application:" | head -1)" ]]; then
CODE_SIGN_NAME=$(security find-certificate -c "Developer ID Application" | cut -d'"' -f 4 -s | grep "Developer ID Application:" | head -1)
if [[ -n "$(security find-certificate -c "Developer ID Application" cloudflared_build_keychain | cut -d'"' -f 4 -s | grep "Developer ID Application:" | head -1)" ]]; then
CODE_SIGN_NAME=$(security find-certificate -c "Developer ID Application" cloudflared_build_keychain | cut -d'"' -f 4 -s | grep "Developer ID Application:" | head -1)
else
CODE_SIGN_NAME=""
fi
@@ -130,8 +155,8 @@ fi
if [[ ! -z "$CFD_INSTALLER_NAME" ]]; then
PKG_SIGN_NAME="${CFD_INSTALLER_NAME}"
else
if [[ -n "$(security find-certificate -c "Developer ID Installer" | cut -d'"' -f 4 -s | grep "Developer ID Installer:" | head -1)" ]]; then
PKG_SIGN_NAME=$(security find-certificate -c "Developer ID Installer" | cut -d'"' -f 4 -s | grep "Developer ID Installer:" | head -1)
if [[ -n "$(security find-certificate -c "Developer ID Installer" cloudflared_build_keychain | cut -d'"' -f 4 -s | grep "Developer ID Installer:" | head -1)" ]]; then
PKG_SIGN_NAME=$(security find-certificate -c "Developer ID Installer" cloudflared_build_keychain | cut -d'"' -f 4 -s | grep "Developer ID Installer:" | head -1)
else
PKG_SIGN_NAME=""
fi
@@ -142,9 +167,16 @@ rm -rf "${TARGET_DIRECTORY}"
export TARGET_OS="darwin"
GOCACHE="$PWD/../../../../" GOPATH="$PWD/../../../../" CGO_ENABLED=1 make cloudflared
# This allows apple tools to use the certificates in the keychain without requiring password input.
# This command always needs to run after the certificates have been loaded into the keychain
if [[ ! -z "$CFD_CODE_SIGN_PASS" ]]; then
security set-key-partition-list -S apple-tool:,apple: -s -k "${CFD_CODE_SIGN_PASS}" cloudflared_build_keychain
fi
# sign the cloudflared binary
if [[ ! -z "$CODE_SIGN_NAME" ]]; then
codesign -s "${CODE_SIGN_NAME}" -f -v --timestamp --options runtime ${BINARY_NAME}
codesign --keychain $HOME/Library/Keychains/cloudflared_build_keychain.keychain-db -s "${CODE_SIGN_NAME}" -fv --options runtime --timestamp ${BINARY_NAME}
# notarize the binary
# TODO: TUN-5789
@@ -165,11 +197,13 @@ tar czf "$FILENAME" "${BINARY_NAME}"
# build the installer package
if [[ ! -z "$PKG_SIGN_NAME" ]]; then
pkgbuild --identifier com.cloudflare.${PRODUCT} \
--version ${VERSION} \
--scripts ${ARCH_TARGET_DIRECTORY}/scripts \
--root ${ARCH_TARGET_DIRECTORY}/contents \
--install-location /usr/local/bin \
--keychain cloudflared_build_keychain \
--sign "${PKG_SIGN_NAME}" \
${PKGNAME}
@@ -187,3 +221,8 @@ fi
# cleanup build directory because this script is not ran within containers,
# which might lead to future issues in subsequent runs.
rm -rf "${TARGET_DIRECTORY}"
# cleanup the keychain
security default-keychain -d user -s login.keychain-db
security list-keychains -d user -s login.keychain-db
security delete-keychain cloudflared_build_keychain
+1 -1
View File
@@ -7,4 +7,4 @@ rm -rf $GOCACHE
export PATH="/tmp/go/bin:$PATH"
go version
which go
go env
go env
Vendored Executable
+10
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+4
View File
@@ -237,6 +237,10 @@ github-release:
python3 github_release.py --path $(PWD)/built_artifacts --release-version $(VERSION)
python3 github_message.py --release-version $(VERSION)
.PHONY: macos-release
macos-release:
python3 github_release.py --path $(PWD)/artifacts/ --release-version $(VERSION)
.PHONY: r2-linux-release
r2-linux-release:
python3 ./release_pkgs.py
+15
View File
@@ -1,3 +1,18 @@
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
- 2025-04-24 TUN-9255: Improve flush on write conditions in http2 tunnel type to match what is done on the edge
- 2025-04-10 SDLC-3727 - Adding FIPS status to backstage
2025.4.0
- 2025-04-02 Fix broken links in `cmd/cloudflared/*.go` related to running tunnel as a service
- 2025-04-02 chore: remove repetitive words
+2
View File
@@ -13,3 +13,5 @@ spec:
type: "service"
lifecycle: "Active"
owner: "teams/tunnel-teams-routing"
cf:
FIPS: "required"
+1 -1
View File
@@ -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
View File
@@ -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
+74
View File
@@ -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)
}
+50
View File
@@ -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},
}
}
+44 -9
View File
@@ -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
}
-37
View File
@@ -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
}
+4 -12
View File
@@ -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,
-15
View File
@@ -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)
}
+17 -24
View File
@@ -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),
+1 -1
View File
@@ -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))
}
+2 -2
View File
@@ -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()
+26 -6
View File
@@ -26,14 +26,20 @@ const (
MaxGracePeriod = time.Minute * 3
MaxConcurrentStreams = math.MaxUint32
contentTypeHeader = "content-type"
sseContentType = "text/event-stream"
grpcContentType = "application/grpc"
contentTypeHeader = "content-type"
contentLengthHeader = "content-length"
transferEncodingHeader = "transfer-encoding"
sseContentType = "text/event-stream"
grpcContentType = "application/grpc"
sseJsonContentType = "application/x-ndjson"
chunkTransferEncoding = "chunked"
)
var (
switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols, http.StatusText(http.StatusSwitchingProtocols))
flushableContentTypes = []string{sseContentType, grpcContentType}
flushableContentTypes = []string{sseContentType, grpcContentType, sseJsonContentType}
)
// TunnelConnection represents the connection to the edge.
@@ -51,7 +57,6 @@ type Orchestrator interface {
type TunnelProperties struct {
Credentials Credentials
Client pogs.ClientInfo
QuickTunnelUrl string
}
@@ -274,6 +279,22 @@ type ConnectedFuse interface {
// Helper method to let the caller know what content-types should require a flush on every
// write to a ResponseWriter.
func shouldFlush(headers http.Header) bool {
// When doing Server Side Events (SSE), some frameworks don't respect the `Content-Type` header.
// Therefore, we need to rely on other ways to know whether we should flush on write or not. A good
// approach is to assume that responses without `Content-Length` or with `Transfer-Encoding: chunked`
// are streams, and therefore, should be flushed right away to the eyeball.
// References:
// - https://datatracker.ietf.org/doc/html/rfc7230#section-4.1
// - https://datatracker.ietf.org/doc/html/rfc9112#section-6.1
if contentLength := headers.Get(contentLengthHeader); contentLength == "" {
return true
}
if transferEncoding := headers.Get(transferEncodingHeader); transferEncoding != "" {
transferEncoding = strings.ToLower(transferEncoding)
if strings.Contains(transferEncoding, chunkTransferEncoding) {
return true
}
}
if contentType := headers.Get(contentTypeHeader); contentType != "" {
contentType = strings.ToLower(contentType)
for _, c := range flushableContentTypes {
@@ -282,7 +303,6 @@ func shouldFlush(headers http.Header) bool {
}
}
}
return false
}
+47
View File
@@ -7,10 +7,12 @@ import (
"io"
"math/big"
"net/http"
"testing"
"time"
pkgerrors "github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
cfdflow "github.com/cloudflare/cloudflared/flow"
@@ -209,3 +211,48 @@ func (mcf mockConnectedFuse) Connected() {}
func (mcf mockConnectedFuse) IsConnected() bool {
return true
}
func TestShouldFlushHeaders(t *testing.T) {
tests := []struct {
headers map[string]string
shouldFlush bool
}{
{
headers: map[string]string{contentTypeHeader: "application/json", contentLengthHeader: "1"},
shouldFlush: false,
},
{
headers: map[string]string{contentTypeHeader: "text/html", contentLengthHeader: "1"},
shouldFlush: false,
},
{
headers: map[string]string{contentTypeHeader: "text/event-stream", contentLengthHeader: "1"},
shouldFlush: true,
},
{
headers: map[string]string{contentTypeHeader: "application/grpc", contentLengthHeader: "1"},
shouldFlush: true,
},
{
headers: map[string]string{contentTypeHeader: "application/x-ndjson", contentLengthHeader: "1"},
shouldFlush: true,
},
{
headers: map[string]string{contentTypeHeader: "application/json"},
shouldFlush: true,
},
{
headers: map[string]string{contentTypeHeader: "application/json", contentLengthHeader: "-1", transferEncodingHeader: "chunked"},
shouldFlush: true,
},
}
for _, test := range tests {
headers := http.Header{}
for k, v := range test.headers {
headers.Add(k, v)
}
require.Equal(t, test.shouldFlush, shouldFlush(headers))
}
}
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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() {
+5 -5
View File
@@ -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)
}
+13 -13
View File
@@ -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,
+7 -4
View File
@@ -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)
}
+15 -4
View File
@@ -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
}
+1 -1
View File
@@ -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
}
+19 -18
View File
@@ -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
-10
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
+11 -4
View File
@@ -61,7 +61,7 @@ def assert_tag_exists(repo, version):
raise Exception("Tag {} not found".format(version))
def get_or_create_release(repo, version, dry_run=False):
def get_or_create_release(repo, version, dry_run=False, is_draft=False):
"""
Get a Github Release matching the version tag or create a new one.
If a conflict occurs on creation, attempt to fetch the Release on last time
@@ -81,8 +81,11 @@ def get_or_create_release(repo, version, dry_run=False):
return
try:
logging.info("Creating release %s", version)
return repo.create_git_release(version, version, "")
if is_draft:
logging.info("Drafting release %s", version)
else:
logging.info("Creating release %s", version)
return repo.create_git_release(version, version, "", is_draft)
except GithubException as e:
errors = e.data.get("errors", [])
if e.status == 422 and any(
@@ -129,6 +132,10 @@ def parse_args():
"--dry-run", action="store_true", help="Do not create release or upload asset"
)
parser.add_argument(
"--draft", action="store_true", help="Create a draft release"
)
args = parser.parse_args()
is_valid = True
if not args.release_version:
@@ -292,7 +299,7 @@ def main():
for filename in onlyfiles:
binary_path = os.path.join(args.path, filename)
assert_asset_version(binary_path, args.release_version)
release = get_or_create_release(repo, args.release_version, args.dry_run)
release = get_or_create_release(repo, args.release_version, args.dry_run, args.draft)
for filename in onlyfiles:
binary_path = os.path.join(args.path, filename)
upload_asset(release, binary_path, filename, args.release_version, args.kv_account_id, args.namespace_id,
+21 -24
View File
@@ -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
+44 -76
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
//go:build windows
//go:build windows && cgo
package ingress
+4 -4
View File
@@ -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)
+2 -3
View File
@@ -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) {
+8 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+6 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -17,6 +17,7 @@ import (
"github.com/rs/zerolog"
"golang.org/x/sync/errgroup"
"github.com/cloudflare/cloudflared/client"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/edgediscovery"
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
@@ -38,10 +39,8 @@ const (
)
type TunnelConfig struct {
ClientConfig *client.Config
GracePeriod time.Duration
ReplaceExisting bool
OSArch string
ClientID string
CloseConnOnce *sync.Once // Used to close connectedSignal no more than once
EdgeAddrs []string
Region string
@@ -72,22 +71,13 @@ type TunnelConfig struct {
DisableQUICPathMTUDiscovery bool
QUICConnectionLevelFlowControlLimit uint64
QUICStreamLevelFlowControlLimit uint64
FeatureSelector *features.FeatureSelector
}
func (c *TunnelConfig) connectionOptions(originLocalAddr string, numPreviousAttempts uint8) *pogs.ConnectionOptions {
func (c *TunnelConfig) connectionOptions(originLocalAddr string, previousAttempts uint8) *client.ConnectionOptionsSnapshot {
// attempt to parse out origin IP, but don't fail since it's informational field
host, _, _ := net.SplitHostPort(originLocalAddr)
originIP := net.ParseIP(host)
return &pogs.ConnectionOptions{
Client: c.NamedTunnel.Client,
OriginLocalIP: originIP,
ReplaceExisting: c.ReplaceExisting,
CompressionQuality: 0,
NumPreviousAttempts: numPreviousAttempts,
}
return c.ClientConfig.ConnectionOptionsSnapshot(originIP, previousAttempts)
}
func StartTunnelDaemon(
@@ -463,6 +453,8 @@ func (e *EdgeTunnelServer) serveConnection(
case connection.QUIC:
// nolint: gosec
connOptions := e.config.connectionOptions(addr.UDP.String(), uint8(backoff.Retries()))
// nolint: zerologlint
connOptions.LogFields(connLog.Logger().Debug().Uint8(connection.LogFieldConnIndex, connIndex)).Msgf("Tunnel connection options")
return e.serveQUIC(ctx,
addr.UDP.AddrPort(),
connLog,
@@ -479,6 +471,8 @@ func (e *EdgeTunnelServer) serveConnection(
// nolint: gosec
connOptions := e.config.connectionOptions(edgeConn.LocalAddr().String(), uint8(backoff.Retries()))
// nolint: zerologlint
connOptions.LogFields(connLog.Logger().Debug().Uint8(connection.LogFieldConnIndex, connIndex)).Msgf("Tunnel connection options")
if err := e.serveHTTP2(
ctx,
connLog,
@@ -508,11 +502,11 @@ func (e *EdgeTunnelServer) serveHTTP2(
ctx context.Context,
connLog *ConnAwareLogger,
tlsServerConn net.Conn,
connOptions *pogs.ConnectionOptions,
connOptions *client.ConnectionOptionsSnapshot,
controlStreamHandler connection.ControlStreamHandler,
connIndex uint8,
) error {
pqMode := e.config.FeatureSelector.PostQuantumMode()
pqMode := connOptions.FeatureSnapshot.PostQuantum
if pqMode == features.PostQuantumStrict {
return unrecoverableError{errors.New("HTTP/2 transport does not support post-quantum")}
}
@@ -550,19 +544,19 @@ func (e *EdgeTunnelServer) serveQUIC(
ctx context.Context,
edgeAddr netip.AddrPort,
connLogger *ConnAwareLogger,
connOptions *pogs.ConnectionOptions,
connOptions *client.ConnectionOptionsSnapshot,
controlStreamHandler connection.ControlStreamHandler,
connIndex uint8,
) (err error, recoverable bool) {
tlsConfig := e.config.EdgeTLSConfigs[connection.QUIC]
pqMode := e.config.FeatureSelector.PostQuantumMode()
pqMode := connOptions.FeatureSnapshot.PostQuantum
curvePref, err := curvePreference(pqMode, fips.IsFipsEnabled(), tlsConfig.CurvePreferences)
if err != nil {
return err, true
}
connLogger.Logger().Info().Msgf("Using %v as curve preferences", curvePref)
connLogger.Logger().Info().Msgf("Tunnel connection curve preferences: %v", curvePref)
tlsConfig.CurvePreferences = curvePref
@@ -600,12 +594,12 @@ func (e *EdgeTunnelServer) serveQUIC(
if err != nil {
connLogger.ConnAwareLogger().Err(err).Msgf("Failed to dial a quic connection")
e.reportErrorToSentry(err)
e.reportErrorToSentry(err, connOptions.FeatureSnapshot.PostQuantum)
return err, true
}
var datagramSessionManager connection.DatagramSessionHandler
if e.config.FeatureSelector.DatagramVersion() == features.DatagramV3 {
if connOptions.FeatureSnapshot.DatagramVersion == features.DatagramV3 {
datagramSessionManager = connection.NewDatagramV3Connection(
ctx,
conn,
@@ -672,7 +666,7 @@ func (e *EdgeTunnelServer) serveQUIC(
// The reportErrorToSentry is an helper function that handles
// verifies if an error should be reported to Sentry.
func (e *EdgeTunnelServer) reportErrorToSentry(err error) {
func (e *EdgeTunnelServer) reportErrorToSentry(err error, pqMode features.PostQuantumMode) {
dialErr, ok := err.(*connection.EdgeQuicDialError)
if ok {
// The TransportError provides an Unwrap function however
@@ -681,7 +675,7 @@ func (e *EdgeTunnelServer) reportErrorToSentry(err error) {
if ok &&
transportErr.ErrorCode.IsCryptoError() &&
fips.IsFipsEnabled() &&
e.config.FeatureSelector.PostQuantumMode() == features.PostQuantumStrict {
pqMode == features.PostQuantumStrict {
// Only report to Sentry when using FIPS, PQ,
// and the error is a Crypto error reported by
// an EdgeQuicDialError
+1 -1
View File
@@ -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
}
+2 -2
View File
@@ -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()
+11 -11
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,6 +1,7 @@
# A minimal logging API for Go
[![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/logr.svg)](https://pkg.go.dev/github.com/go-logr/logr)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-logr/logr)](https://goreportcard.com/report/github.com/go-logr/logr)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/go-logr/logr/badge)](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
View File
@@ -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()
}
@@ -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
@@ -1,4 +1,4 @@
# Slim-Sprig: Template functions for Go templates [![GoDoc](https://godoc.org/github.com/go-task/slim-sprig?status.svg)](https://godoc.org/github.com/go-task/slim-sprig) [![Go Report Card](https://goreportcard.com/badge/github.com/go-task/slim-sprig)](https://goreportcard.com/report/github.com/go-task/slim-sprig)
# Slim-Sprig: Template functions for Go templates [![Go Reference](https://pkg.go.dev/badge/github.com/go-task/slim-sprig/v3.svg)](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
@@ -1,6 +1,6 @@
# https://taskfile.dev
version: '2'
version: '3'
tasks:
default:
+8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"

Some files were not shown because too many files have changed in this diff Show More