mirror of
https://github.com/cloudflare/cloudflared.git
synced 2026-08-07 15:24:46 +00:00
Compare commits
257 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5745eed230 | |||
| c24f275981 | |||
| d8f2b768f8 | |||
| 93e569fa23 | |||
| 207f4e2c8d | |||
| 513855df5c | |||
| bd917d294c | |||
| 4616e9fcc2 | |||
| de7ca4be30 | |||
| 4d993488df | |||
| 794e8e622f | |||
| 87bd36c924 | |||
| de4fd472f3 | |||
| 887e486a63 | |||
| 645e22744c | |||
| d19da6767a | |||
| 045439f0ab | |||
| 2519aec733 | |||
| 99b3736cc7 | |||
| e517242194 | |||
| 7dee179652 | |||
| 78ca8002d2 | |||
| c13b6df0a7 | |||
| b8b35d99fa | |||
| 61ccc0b303 | |||
| 7ef9bb89d3 | |||
| 45e8eb7275 | |||
| 72503eeaaa | |||
| 09e33a0b17 | |||
| 4c10f68e2d | |||
| cf87ec7969 | |||
| 64f15d9992 | |||
| e3d35570e6 | |||
| b0663dce33 | |||
| af59851f33 | |||
| c49621c723 | |||
| 9339bb9485 | |||
| 19106cd609 | |||
| b50f172bdb | |||
| 1c6316c1c9 | |||
| 1fe4878264 | |||
| 85b44695f0 | |||
| 6a1dad0ce2 | |||
| 2baea15387 | |||
| a1d88a6cdd | |||
| 515ad7cbee | |||
| 1b5313cc28 | |||
| dde83d5a7c | |||
| e14238224d | |||
| 66d1f27507 | |||
| e6c9ec0b39 | |||
| c3c050aa79 | |||
| b1de2a74fa | |||
| 4d32a64f98 | |||
| 11f4d10174 | |||
| 60a12fcb27 | |||
| 442af9ee38 | |||
| 2e895c3a4f | |||
| e9d07e35c7 | |||
| 2d5234e021 | |||
| b6bd8c1f5e | |||
| 495f9fb8bd | |||
| 225c344ceb | |||
| 61007dd2dd | |||
| b01006fe46 | |||
| 872cb003a4 | |||
| 2aca844570 | |||
| 90e5255a0d | |||
| 4aead129ed | |||
| 9904929b83 | |||
| c280d62fe5 | |||
| 40ea6a5080 | |||
| 4642316167 | |||
| d0c10b34dd | |||
| f4ae8d1446 | |||
| e89bceca5e | |||
| 6be36fa2c5 | |||
| f81d35447e | |||
| 49438f30f5 | |||
| eacc8c648d | |||
| 5b30925773 | |||
| d7fb18be22 | |||
| cbf8c71fab | |||
| 870193c064 | |||
| fdddd86380 | |||
| b3e26420c0 | |||
| be0305ec58 | |||
| 3449ea35f2 | |||
| 7f487c2651 | |||
| 9bb7628fbc | |||
| eb36716ba4 | |||
| 5d6b0642db | |||
| 462d2f87df | |||
| 0aa21f302e | |||
| de07da02cd | |||
| e9a2c85671 | |||
| b0f0741a9b | |||
| db4564e5b9 | |||
| 3d345d3748 | |||
| b1995b4dd1 | |||
| b457cca1e5 | |||
| a0b6ba9b8d | |||
| de00396669 | |||
| 013bdbd10c | |||
| b639b6627a | |||
| e454994e3e | |||
| 8a53c1aa1d | |||
| f5f3e6a453 | |||
| 30c529e730 | |||
| bf3d70d1d2 | |||
| a65f8bce7f | |||
| 2ffff0687b | |||
| e380333520 | |||
| 11cbff4ff7 | |||
| 3e0ff3a771 | |||
| 7a19798682 | |||
| 4b75943d59 | |||
| fc20a22685 | |||
| faa86ffeca | |||
| f7a14d9200 | |||
| 902e5beb4f | |||
| 7ca5f7569a | |||
| 4ac68711cd | |||
| 075ac1acf1 | |||
| cfef0e737f | |||
| 8ec0f7746b | |||
| 2b3707e2b9 | |||
| 7e760f9fcc | |||
| efb99d90d7 | |||
| e131125558 | |||
| af6bf5c4e5 | |||
| e3390fcb15 | |||
| fc5749328d | |||
| 59f5b0df83 | |||
| f6bd4aa039 | |||
| d2bc15e224 | |||
| bad2e8e812 | |||
| 20ed7557f9 | |||
| 8e9e1d973e | |||
| a97673e8b9 | |||
| e123bbe1c5 | |||
| 906eb2d840 | |||
| e09c62a796 | |||
| bd88093de0 | |||
| 0538953a39 | |||
| 88235356d5 | |||
| 99f39225f1 | |||
| 278df5478a | |||
| d3fd581b7b | |||
| 68d370af19 | |||
| 679a89c7df | |||
| a768132d37 | |||
| 9de4e88ca6 | |||
| 91eba53035 | |||
| 065d8355c5 | |||
| 4016334efc | |||
| d4d9a43dd7 | |||
| 046a30e3c7 | |||
| 7a9207a6e1 | |||
| b9cba7f2ae | |||
| 7f1c890a82 | |||
| f48a7cd3dd | |||
| d96c39196d | |||
| 032ba7b5e4 | |||
| e63ec34503 | |||
| 2a177e0fc4 | |||
| 1733fe8c65 | |||
| 06f7ba4523 | |||
| 7607ead143 | |||
| ac7fdd5572 | |||
| f3ba506880 | |||
| d2cb803336 | |||
| efd4556546 | |||
| 2e2718b7e3 | |||
| b849def673 | |||
| dd540af695 | |||
| e921ab35d5 | |||
| ae7fbc14f3 | |||
| 2fa50acc2d | |||
| c7a6304d32 | |||
| f4667c6345 | |||
| 6a6ba704f1 | |||
| 135c8e6d13 | |||
| 420e80ea50 | |||
| 337591b2bb | |||
| fa6bcdad04 | |||
| ee87c43eb9 | |||
| bccc58b54d | |||
| 3da1c25471 | |||
| 7d0a271000 | |||
| 102631d98d | |||
| 6c3d2fc339 | |||
| 1d79831651 | |||
| 0458ad41dd | |||
| b9453b84bb | |||
| ab81ff8bfb | |||
| f2339a7244 | |||
| 978e01f77e | |||
| 1275930f99 | |||
| 8e9091cc48 | |||
| 76add5ca77 | |||
| e8407848ec | |||
| 29d809535e | |||
| cc1c6d9abc | |||
| 69b28e358c | |||
| 5ed3d4e29a | |||
| 4f468b8a5d | |||
| e3aad7799e | |||
| cc8aa0efb5 | |||
| 475939a77f | |||
| 4ccef23dbc | |||
| 2b0d704777 | |||
| ee80e55833 | |||
| 2345720b2b | |||
| d714a62bd3 | |||
| 7d4afd4ae0 | |||
| 056693c814 | |||
| 73d948bc32 | |||
| 5e6f606f4e | |||
| 919227fc91 | |||
| b8ba5b444c | |||
| baed5f4eea | |||
| 08a8101308 | |||
| a2a4b06eb4 | |||
| ec509e114a | |||
| 7bc2462e36 | |||
| 92f647d45c | |||
| b2ac885370 | |||
| 2c480a72db | |||
| 32739e9f98 | |||
| 7ce2bb8b2f | |||
| 6f78ccde04 | |||
| 26a7b59f6f | |||
| 4b6437cc60 | |||
| f7fd4ea71c | |||
| 7bcab138c5 | |||
| f758361730 | |||
| fa2234d639 | |||
| 99d4e48656 | |||
| 0180b6d733 | |||
| 9ef6191515 | |||
| 2cf43abe8c | |||
| 46c147a1b2 | |||
| 1e71202c89 | |||
| 8250708b37 | |||
| 7499e5fa00 | |||
| e8fe34773c | |||
| 3254d08173 | |||
| d68ad89159 | |||
| f3244db861 | |||
| d22cb4a6ca | |||
| 8f0498f66a | |||
| a97233bb3e | |||
| 775c2bc93e | |||
| e4278bab97 | |||
| f81b0ee9e8 | |||
| 8a07a900fd |
+5
-3
@@ -1,10 +1,12 @@
|
||||
images:
|
||||
- name: cloudflared
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile.$ARCH
|
||||
context: .
|
||||
versions:
|
||||
- latest
|
||||
version_file: versions
|
||||
registries:
|
||||
- name: docker.io/cloudflare
|
||||
user: env:DOCKER_USER
|
||||
password: env:DOCKER_PASSWORD
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
---
|
||||
name: "Bug report \U0001F41B"
|
||||
name: "\U0001F41B Bug report"
|
||||
about: Create a report to help us improve cloudflared
|
||||
title: ''
|
||||
labels: awaiting reply, bug
|
||||
assignees: ''
|
||||
title: "\U0001F41B"
|
||||
labels: 'Priority: Normal, Type: Bug'
|
||||
|
||||
---
|
||||
|
||||
@@ -16,6 +15,10 @@ Steps to reproduce the behavior:
|
||||
2. Run '....'
|
||||
3. See error
|
||||
|
||||
If it's an issue with Cloudflare Tunnel:
|
||||
4. Tunnel ID :
|
||||
5. cloudflared config:
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: "\U0001F4DD Documentation"
|
||||
about: Request new or updated documentation for cloudflared
|
||||
title: "\U0001F4DD"
|
||||
labels: 'Priority: Normal, Type: Documentation'
|
||||
|
||||
---
|
||||
|
||||
**Available Documentation**
|
||||
A link to the documentation that is available today and the areas which could be improved.
|
||||
|
||||
**Suggested Documentation**
|
||||
A clear and concise description of the documentation, tutorial, or guide that should be added.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the documentation request here.
|
||||
+3
-4
@@ -1,9 +1,8 @@
|
||||
---
|
||||
name: "Feature request \U0001F4A1"
|
||||
name: "\U0001F4A1 Feature request"
|
||||
about: Suggest a feature or enhancement for cloudflared
|
||||
title: ''
|
||||
labels: awaiting reply, feature-request
|
||||
assignees: ''
|
||||
title: "\U0001F4A1"
|
||||
labels: 'Priority: Normal, Type: Feature Request'
|
||||
|
||||
---
|
||||
|
||||
@@ -4,7 +4,7 @@ jobs:
|
||||
check:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.17.x]
|
||||
go-version: [1.19.x]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@@ -12,8 +12,6 @@ jobs:
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Install go-sumtype
|
||||
run: go get github.com/sudarshan-reddy/go-sumtype
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Test
|
||||
|
||||
Vendored
+11
-14
@@ -1,5 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -exo pipefail
|
||||
|
||||
if [[ "$(uname)" != "Darwin" ]] ; then
|
||||
echo "This should be run on macOS"
|
||||
exit 1
|
||||
@@ -33,7 +35,9 @@ if [[ ! -z "$CFD_CODE_SIGN_KEY" ]]; then
|
||||
if [[ ! -z "$CFD_CODE_SIGN_PASS" ]]; then
|
||||
# write private key to disk and then import it keychain
|
||||
echo -n -e ${CFD_CODE_SIGN_KEY} | base64 -D > ${CODE_SIGN_PRIV}
|
||||
out=$(security import ${CODE_SIGN_PRIV} -A -P "${CFD_CODE_SIGN_PASS}" 2>&1)
|
||||
# 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.
|
||||
out=$(security import ${CODE_SIGN_PRIV} -A -P "${CFD_CODE_SIGN_PASS}" 2>&1) || true
|
||||
exitcode=$?
|
||||
if [ -n "$out" ]; then
|
||||
if [ $exitcode -eq 0 ]; then
|
||||
@@ -53,7 +57,7 @@ fi
|
||||
if [[ ! -z "$CFD_CODE_SIGN_CERT" ]]; then
|
||||
# write certificate to disk and then import it keychain
|
||||
echo -n -e ${CFD_CODE_SIGN_CERT} | base64 -D > ${CODE_SIGN_CERT}
|
||||
out1=$(security import ${CODE_SIGN_CERT} -A 2>&1)
|
||||
out1=$(security import ${CODE_SIGN_CERT} -A 2>&1) || true
|
||||
exitcode1=$?
|
||||
if [ -n "$out1" ]; then
|
||||
if [ $exitcode1 -eq 0 ]; then
|
||||
@@ -75,7 +79,7 @@ if [[ ! -z "$CFD_INSTALLER_KEY" ]]; then
|
||||
if [[ ! -z "$CFD_INSTALLER_PASS" ]]; then
|
||||
# write private key to disk and then import it into the keychain
|
||||
echo -n -e ${CFD_INSTALLER_KEY} | base64 -D > ${INSTALLER_PRIV}
|
||||
out2=$(security import ${INSTALLER_PRIV} -A -P "${CFD_INSTALLER_PASS}" 2>&1)
|
||||
out2=$(security import ${INSTALLER_PRIV} -A -P "${CFD_INSTALLER_PASS}" 2>&1) || true
|
||||
exitcode2=$?
|
||||
if [ -n "$out2" ]; then
|
||||
if [ $exitcode2 -eq 0 ]; then
|
||||
@@ -95,7 +99,7 @@ fi
|
||||
if [[ ! -z "$CFD_INSTALLER_CERT" ]]; then
|
||||
# write certificate to disk and then import it keychain
|
||||
echo -n -e ${CFD_INSTALLER_CERT} | base64 -D > ${INSTALLER_CERT}
|
||||
out3=$(security import ${INSTALLER_CERT} -A 2>&1)
|
||||
out3=$(security import ${INSTALLER_CERT} -A 2>&1) || true
|
||||
exitcode3=$?
|
||||
if [ -n "$out3" ]; then
|
||||
if [ $exitcode3 -eq 0 ]; then
|
||||
@@ -138,14 +142,10 @@ fi
|
||||
if [[ ! -z "$CODE_SIGN_NAME" ]]; then
|
||||
codesign -s "${CODE_SIGN_NAME}" -f -v --timestamp --options runtime ${BINARY_NAME}
|
||||
|
||||
# notarize the binary
|
||||
if [[ ! -z "$CFD_NOTE_PASSWORD" ]]; then
|
||||
zip "${BINARY_NAME}.zip" ${BINARY_NAME}
|
||||
xcrun altool --notarize-app -f "${BINARY_NAME}.zip" -t osx -u ${CFD_NOTE_USERNAME} -p ${CFD_NOTE_PASSWORD} --primary-bundle-id ${BUNDLE_ID}
|
||||
fi
|
||||
# notarize the binary
|
||||
# TODO: https://jira.cfdata.org/browse/TUN-5789
|
||||
fi
|
||||
|
||||
|
||||
# creating build directory
|
||||
rm -rf $TARGET_DIRECTORY
|
||||
mkdir "${TARGET_DIRECTORY}"
|
||||
@@ -169,10 +169,7 @@ if [[ ! -z "$PKG_SIGN_NAME" ]]; then
|
||||
${PKGNAME}
|
||||
|
||||
# notarize the package
|
||||
if [[ ! -z "$CFD_NOTE_PASSWORD" ]]; then
|
||||
xcrun altool --notarize-app -f ${PKGNAME} -t osx -u ${CFD_NOTE_USERNAME} -p ${CFD_NOTE_PASSWORD} --primary-bundle-id ${BUNDLE_ID}
|
||||
xcrun stapler staple ${PKGNAME}
|
||||
fi
|
||||
# TODO: https://jira.cfdata.org/browse/TUN-5789
|
||||
else
|
||||
pkgbuild --identifier com.cloudflare.${PRODUCT} \
|
||||
--version ${VERSION} \
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
VERSION=$(git describe --tags --always --match "[0-9][0-9][0-9][0-9].*.*")
|
||||
echo $VERSION
|
||||
|
||||
export TARGET_OS=windows
|
||||
# This controls the directory the built artifacts go into
|
||||
export ARTIFACT_DIR=built_artifacts/
|
||||
mkdir -p $ARTIFACT_DIR
|
||||
windowsArchs=("amd64" "386")
|
||||
for arch in ${windowsArchs[@]}; do
|
||||
export TARGET_ARCH=$arch
|
||||
# Copy exe into final directory
|
||||
cp ./artifacts/cloudflared-windows-$arch.exe $ARTIFACT_DIR/cloudflared-windows-$arch.exe
|
||||
cp ./artifacts/cloudflared-windows-$arch.exe ./cloudflared.exe
|
||||
make cloudflared-msi
|
||||
# Copy msi into final directory
|
||||
mv cloudflared-$VERSION-$arch.msi $ARTIFACT_DIR/cloudflared-windows-$arch.msi
|
||||
done
|
||||
Vendored
+4
-1
@@ -19,5 +19,8 @@ eval "$(tmp/homebrew/bin/brew shellenv)"
|
||||
brew update --force --quiet
|
||||
chmod -R go-w "$(brew --prefix)/share/zsh"
|
||||
|
||||
git config --global user.name "cloudflare-warp-bot"
|
||||
git config --global user.email "warp-bot@cloudflare.com"
|
||||
|
||||
# bump formula pr
|
||||
brew bump-formula-pr cloudflared --version="$VERSION"
|
||||
brew bump-formula-pr cloudflared --version="$VERSION" --no-browse --no-audit
|
||||
|
||||
+22
@@ -1,3 +1,25 @@
|
||||
## 2022.12.0
|
||||
### Improvements
|
||||
- cloudflared now attempts to try other edge addresses before falling back to a lower protoocol.
|
||||
- cloudflared tunnel no longer spins up a quick tunnel. The call has to be explicit and provide a --url flag.
|
||||
- cloudflared will now randomly pick the first or second region to connect to instead of always connecting to region2 first.
|
||||
|
||||
## 2022.9.0
|
||||
### New Features
|
||||
- cloudflared now rejects ingress rules with invalid http status codes for http_status.
|
||||
|
||||
## 2022.8.1
|
||||
### New Features
|
||||
- cloudflared now remembers if it connected to a certain protocol successfully. If it did, it does not fall back to a lower
|
||||
protocol on connection failures.
|
||||
|
||||
## 2022.7.1
|
||||
### New Features
|
||||
- It is now possible to connect cloudflared tunnel to Cloudflare Global Network with IPv6. See `cloudflared tunnel --help` and look for `edge-ip-version` for more information. For now, the default behavior is to still connect with IPv4 only.
|
||||
|
||||
### Bug Fixes
|
||||
- Several bug fixes related with QUIC transport (used between cloudflared tunnel and Cloudflare Global Network). Updating to this version is highly recommended.
|
||||
|
||||
## 2022.4.0
|
||||
### Bug Fixes
|
||||
- `cloudflared tunnel run` no longer logs the Tunnel token or JSON credentials in clear text as those are the secret
|
||||
|
||||
+5
-3
@@ -1,12 +1,12 @@
|
||||
# use a builder image for building cloudflare
|
||||
ARG TARGET_GOOS
|
||||
ARG TARGET_GOARCH
|
||||
FROM golang:1.17.1 as builder
|
||||
FROM golang:1.19 as builder
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0 \
|
||||
TARGET_GOOS=${TARGET_GOOS} \
|
||||
TARGET_GOARCH=${TARGET_GOARCH}
|
||||
|
||||
|
||||
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
|
||||
# copy our sources into the builder image
|
||||
@@ -16,7 +16,9 @@ COPY . .
|
||||
RUN make cloudflared
|
||||
|
||||
# use a distroless base image with glibc
|
||||
FROM gcr.io/distroless/base-debian10:nonroot
|
||||
FROM gcr.io/distroless/base-debian11:nonroot
|
||||
|
||||
LABEL org.opencontainers.image.source="https://github.com/cloudflare/cloudflared"
|
||||
|
||||
# copy our compiled binary
|
||||
COPY --from=builder --chown=nonroot /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# use a builder image for building cloudflare
|
||||
FROM golang:1.19 as builder
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0
|
||||
|
||||
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
|
||||
# copy our sources into the builder image
|
||||
COPY . .
|
||||
|
||||
# compile cloudflared
|
||||
RUN GOOS=linux GOARCH=amd64 make cloudflared
|
||||
|
||||
# use a distroless base image with glibc
|
||||
FROM gcr.io/distroless/base-debian11:nonroot
|
||||
|
||||
LABEL org.opencontainers.image.source="https://github.com/cloudflare/cloudflared"
|
||||
|
||||
# copy our compiled binary
|
||||
COPY --from=builder --chown=nonroot /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
||||
|
||||
# run as non-privileged user
|
||||
USER nonroot
|
||||
|
||||
# command / entrypoint of container
|
||||
ENTRYPOINT ["cloudflared", "--no-autoupdate"]
|
||||
CMD ["version"]
|
||||
@@ -0,0 +1,27 @@
|
||||
# use a builder image for building cloudflare
|
||||
FROM golang:1.19 as builder
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0
|
||||
|
||||
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
|
||||
# copy our sources into the builder image
|
||||
COPY . .
|
||||
|
||||
# compile cloudflared
|
||||
RUN GOOS=linux GOARCH=arm64 make cloudflared
|
||||
|
||||
# use a distroless base image with glibc
|
||||
FROM gcr.io/distroless/base-debian11:nonroot-arm64
|
||||
|
||||
LABEL org.opencontainers.image.source="https://github.com/cloudflare/cloudflared"
|
||||
|
||||
# copy our compiled binary
|
||||
COPY --from=builder --chown=nonroot /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
||||
|
||||
# run as non-privileged user
|
||||
USER nonroot
|
||||
|
||||
# command / entrypoint of container
|
||||
ENTRYPOINT ["cloudflared", "--no-autoupdate"]
|
||||
CMD ["version"]
|
||||
@@ -40,6 +40,10 @@ ifneq ($(GO_BUILD_TAGS),)
|
||||
GO_BUILD_TAGS := -tags "$(GO_BUILD_TAGS)"
|
||||
endif
|
||||
|
||||
ifeq ($(debug), 1)
|
||||
GO_BUILD_TAGS += -gcflags="all=-N -l"
|
||||
endif
|
||||
|
||||
IMPORT_PATH := github.com/cloudflare/cloudflared
|
||||
PACKAGE_DIR := $(CURDIR)/packaging
|
||||
PREFIX := /usr
|
||||
@@ -94,6 +98,19 @@ else
|
||||
TARGET_PUBLIC_REPO ?= $(FLAVOR)
|
||||
endif
|
||||
|
||||
ifneq ($(TARGET_ARM), )
|
||||
ARM_COMMAND := GOARM=$(TARGET_ARM)
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET_ARM), 7)
|
||||
PACKAGE_ARCH := armhf
|
||||
else
|
||||
PACKAGE_ARCH := $(TARGET_ARCH)
|
||||
endif
|
||||
|
||||
#for FIPS compliance, FPM defaults to MD5.
|
||||
RPM_DIGEST := --rpm-digest sha256
|
||||
|
||||
.PHONY: all
|
||||
all: cloudflared test
|
||||
|
||||
@@ -107,7 +124,7 @@ ifeq ($(FIPS), true)
|
||||
$(info Building cloudflared with go-fips)
|
||||
cp -f fips/fips.go.linux-amd64 cmd/cloudflared/fips.go
|
||||
endif
|
||||
GOOS=$(TARGET_OS) GOARCH=$(TARGET_ARCH) go build -v -mod=vendor $(GO_BUILD_TAGS) $(LDFLAGS) $(IMPORT_PATH)/cmd/cloudflared
|
||||
GOOS=$(TARGET_OS) GOARCH=$(TARGET_ARCH) $(ARM_COMMAND) go build -v -mod=vendor $(GO_BUILD_TAGS) $(LDFLAGS) $(IMPORT_PATH)/cmd/cloudflared
|
||||
ifeq ($(FIPS), true)
|
||||
rm -f cmd/cloudflared/fips.go
|
||||
./check-fips.sh cloudflared
|
||||
@@ -117,6 +134,10 @@ endif
|
||||
container:
|
||||
docker build --build-arg=TARGET_ARCH=$(TARGET_ARCH) --build-arg=TARGET_OS=$(TARGET_OS) -t cloudflare/cloudflared-$(TARGET_OS)-$(TARGET_ARCH):"$(VERSION)" .
|
||||
|
||||
.PHONY: generate-docker-version
|
||||
generate-docker-version:
|
||||
echo latest $(VERSION) > versions
|
||||
|
||||
.PHONY: test
|
||||
test: vet
|
||||
ifndef CI
|
||||
@@ -131,24 +152,8 @@ endif
|
||||
test-ssh-server:
|
||||
docker-compose -f ssh_server_tests/docker-compose.yml up
|
||||
|
||||
define publish_package
|
||||
chmod 664 $(BINARY_NAME)*.$(1); \
|
||||
for HOST in $(CF_PKG_HOSTS); do \
|
||||
ssh-keyscan -t ecdsa $$HOST >> ~/.ssh/known_hosts; \
|
||||
scp -p -4 $(BINARY_NAME)*.$(1) cfsync@$$HOST:/state/cf-pkg/staging/$(2)/$(TARGET_PUBLIC_REPO)/$(BINARY_NAME)/; \
|
||||
done
|
||||
endef
|
||||
|
||||
.PHONY: publish-deb
|
||||
publish-deb: cloudflared-deb
|
||||
$(call publish_package,deb,apt)
|
||||
|
||||
.PHONY: publish-rpm
|
||||
publish-rpm: cloudflared-rpm
|
||||
$(call publish_package,rpm,yum)
|
||||
|
||||
cloudflared.1: cloudflared_man_template
|
||||
cat cloudflared_man_template | sed -e 's/\$${VERSION}/$(VERSION)/; s/\$${DATE}/$(DATE)/' > cloudflared.1
|
||||
sed -e 's/\$${VERSION}/$(VERSION)/; s/\$${DATE}/$(DATE)/' cloudflared_man_template > cloudflared.1
|
||||
|
||||
install: cloudflared cloudflared.1
|
||||
mkdir -p $(DESTDIR)$(INSTALL_BINDIR) $(DESTDIR)$(INSTALL_MANDIR)
|
||||
@@ -161,13 +166,13 @@ define build_package
|
||||
mkdir -p $(PACKAGE_DIR)
|
||||
cp cloudflared $(PACKAGE_DIR)/cloudflared
|
||||
cp cloudflared.1 $(PACKAGE_DIR)/cloudflared.1
|
||||
fakeroot fpm -C $(PACKAGE_DIR) -s dir -t $(1) \
|
||||
fpm -C $(PACKAGE_DIR) -s dir -t $(1) \
|
||||
--description 'Cloudflare Tunnel daemon' \
|
||||
--vendor 'Cloudflare' \
|
||||
--license 'Apache License Version 2.0' \
|
||||
--url 'https://github.com/cloudflare/cloudflared' \
|
||||
-m 'Cloudflare <support@cloudflare.com>' \
|
||||
-a $(TARGET_ARCH) -v $(VERSION) -n $(DEB_PACKAGE_NAME) $(NIGHTLY_FLAGS) --after-install postinst.sh --after-remove postrm.sh \
|
||||
-a $(PACKAGE_ARCH) -v $(VERSION) -n $(DEB_PACKAGE_NAME) $(RPM_DIGEST) $(NIGHTLY_FLAGS) --after-install postinst.sh --after-remove postrm.sh \
|
||||
cloudflared=$(INSTALL_BINDIR) cloudflared.1=$(INSTALL_MANDIR)
|
||||
endef
|
||||
|
||||
@@ -184,7 +189,7 @@ cloudflared-pkg: cloudflared cloudflared.1
|
||||
$(call build_package,osxpkg)
|
||||
|
||||
.PHONY: cloudflared-msi
|
||||
cloudflared-msi: cloudflared
|
||||
cloudflared-msi:
|
||||
wixl --define Version=$(VERSION) --define Path=$(EXECUTABLE_PATH) --output cloudflared-$(VERSION)-$(TARGET_ARCH).msi cloudflared.wxs
|
||||
|
||||
.PHONY: cloudflared-darwin-amd64.tgz
|
||||
@@ -261,6 +266,10 @@ github-release: cloudflared
|
||||
github-release-built-pkgs:
|
||||
python3 github_release.py --path $(PWD)/built_artifacts --release-version $(VERSION)
|
||||
|
||||
.PHONY: release-pkgs-linux
|
||||
release-pkgs-linux:
|
||||
python3 ./release_pkgs.py
|
||||
|
||||
.PHONY: github-message
|
||||
github-message:
|
||||
python3 github_message.py --release-version $(VERSION)
|
||||
@@ -270,10 +279,17 @@ github-mac-upload:
|
||||
python3 github_release.py --path artifacts/cloudflared-darwin-amd64.tgz --release-version $(VERSION) --name cloudflared-darwin-amd64.tgz
|
||||
python3 github_release.py --path artifacts/cloudflared-amd64.pkg --release-version $(VERSION) --name cloudflared-amd64.pkg
|
||||
|
||||
.PHONY: github-windows-upload
|
||||
github-windows-upload:
|
||||
python3 github_release.py --path built_artifacts/cloudflared-windows-amd64.exe --release-version $(VERSION) --name cloudflared-windows-amd64.exe
|
||||
python3 github_release.py --path built_artifacts/cloudflared-windows-amd64.msi --release-version $(VERSION) --name cloudflared-windows-amd64.msi
|
||||
python3 github_release.py --path built_artifacts/cloudflared-windows-386.exe --release-version $(VERSION) --name cloudflared-windows-386.exe
|
||||
python3 github_release.py --path built_artifacts/cloudflared-windows-386.msi --release-version $(VERSION) --name cloudflared-windows-386.msi
|
||||
|
||||
.PHONY: tunnelrpc-deps
|
||||
tunnelrpc-deps:
|
||||
which capnp # https://capnproto.org/install.html
|
||||
which capnpc-go # go get zombiezen.com/go/capnproto2/capnpc-go
|
||||
which capnpc-go # go install zombiezen.com/go/capnproto2/capnpc-go@latest
|
||||
capnp compile -ogo tunnelrpc/tunnelrpc.capnp
|
||||
|
||||
.PHONY: quic-deps
|
||||
@@ -284,13 +300,8 @@ quic-deps:
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
go vet -mod=vendor ./...
|
||||
# go get github.com/sudarshan-reddy/go-sumtype (don't do this in build directory or this will cause vendor issues)
|
||||
# Note: If you have github.com/BurntSushi/go-sumtype then you might have to use the repo above instead
|
||||
# for now because it uses an older version of golang.org/x/tools.
|
||||
which go-sumtype
|
||||
go-sumtype $$(go list -mod=vendor ./...)
|
||||
go vet -v -mod=vendor github.com/cloudflare/cloudflared/...
|
||||
|
||||
.PHONY: goimports
|
||||
goimports:
|
||||
for d in $$(go list -mod=readonly -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc) ; do goimports -format-only -local github.com/cloudflare/cloudflared -w $$d ; done
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
goimports -l -w -local github.com/cloudflare/cloudflared $$(go list -mod=vendor -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc)
|
||||
|
||||
@@ -25,12 +25,13 @@ routing), but for legacy reasons this requirement is still necessary:
|
||||
|
||||
## Installing `cloudflared`
|
||||
|
||||
Downloads are available as standalone binaries, a Docker image, and Debian, RPM, and Homebrew packages. You can also find releases here on the `cloudflared` GitHub repository.
|
||||
Downloads are available as standalone binaries, a Docker image, and Debian, RPM, and Homebrew packages. You can also find releases [here](https://github.com/cloudflare/cloudflared/releases) on the `cloudflared` GitHub repository.
|
||||
|
||||
* You can [install on macOS](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation#macos) via Homebrew or by downloading the [latest Darwin amd64 release](https://github.com/cloudflare/cloudflared/releases)
|
||||
* Binaries, Debian, and RPM packages for Linux [can be found here](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation#linux)
|
||||
* A Docker image of `cloudflared` is [available on DockerHub](https://hub.docker.com/r/cloudflare/cloudflared)
|
||||
* You can install on Windows machines with the [steps here](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation#windows)
|
||||
* Build from source with the [instructions here](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation#build-from-source)
|
||||
|
||||
User documentation for Cloudflare Tunnel can be found at https://developers.cloudflare.com/cloudflare-one/connections/connect-apps
|
||||
|
||||
@@ -43,7 +44,7 @@ Once installed, you can authenticate `cloudflared` into your Cloudflare account
|
||||
* Route traffic to that Tunnel:
|
||||
* Via public [DNS records in Cloudflare](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/dns)
|
||||
* Or via a public hostname guided by a [Cloudflare Load Balancer](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/lb)
|
||||
* Or from [WARP client private traffic](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/private-networks)
|
||||
* Or from [WARP client private traffic](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/private-net/)
|
||||
|
||||
|
||||
## TryCloudflare
|
||||
|
||||
+273
@@ -1,3 +1,276 @@
|
||||
2023.2.0
|
||||
- 2023-01-31 TUN-7147: Revert wrong removal of debug endpoint from metrics port
|
||||
- 2023-01-30 TUN-7146: Avoid data race in closing origin connection too early
|
||||
- 2023-01-22 TUN-7097: Fix bug checking proxy-dns config on tunnel cmd execution
|
||||
- 2023-01-20 ZTC-446: Allow to force delete a vnet
|
||||
- 2023-01-17 TUN-7077: Specific name in cloudflare tunnel route lb command
|
||||
- 2023-01-16 TUN-7073: Fix propagating of bad stream request from origin to downstream
|
||||
- 2023-01-11 TUN-7065: Remove classic tunnel creation
|
||||
- 2023-01-10 TUN-7066: Bump coredns to v1.10.0
|
||||
|
||||
2023.1.0
|
||||
- 2023-01-10 TUN-7064: RPM digests are now sha256 instead of md5sum
|
||||
- 2023-01-04 RTG-2418 Update qtls
|
||||
- 2022-12-24 TUN-7057: Remove dependency github.com/gorilla/mux
|
||||
- 2022-12-24 TUN-6724: Migrate to sentry-go from raven-go
|
||||
|
||||
2022.12.1
|
||||
- 2022-12-20 TUN-7021: Fix proxy-dns not starting when cloudflared tunnel is run
|
||||
- 2022-12-15 TUN-7010: Changelog for release 2022.12.0
|
||||
|
||||
2022.12.0
|
||||
- 2022-12-14 TUN-6999: cloudflared should attempt other edge addresses before falling back on protocol
|
||||
- 2022-12-13 TUN-7004: Dont show local config dirs for remotely configured tuns
|
||||
- 2022-12-12 TUN-7003: Tempoarily disable erroneous notarize-app
|
||||
- 2022-12-12 TUN-7003: Add back a missing fi
|
||||
- 2022-12-07 TUN-7000: Reduce metric cardinality of closedConnections metric by removing error as tag
|
||||
- 2022-12-07 TUN-6994: Improve logging config file not found
|
||||
- 2022-12-07 TUN-7002: Randomise first region selection
|
||||
- 2022-12-07 TUN-6995: Disable quick-tunnels spin up by default
|
||||
- 2022-12-05 TUN-6984: Add bash set x to improve visibility during builds
|
||||
- 2022-12-05 TUN-6984: [CI] Ignore security import errors for code_sigining
|
||||
- 2022-12-05 TUN-6984: [CI] Don't fail on unset.
|
||||
- 2022-11-30 TUN-6984: Set euo pipefile for homebrew builds
|
||||
|
||||
2022.11.1
|
||||
- 2022-11-29 TUN-6981: We should close UDP socket if failed to connecto to edge
|
||||
- 2022-11-25 CUSTESC-23757: Fix a bug where a wildcard ingress rule would match an host without starting with a dot
|
||||
- 2022-11-24 TUN-6970: Print newline when printing tunnel token
|
||||
- 2022-11-22 TUN-6963: Refactor Metrics service setup
|
||||
2022.11.0
|
||||
- 2022-11-16 Revert "TUN-6935: Cloudflared should use APIToken instead of serviceKey"
|
||||
- 2022-11-16 TUN-6929: Use same protocol for other connections as first one
|
||||
- 2022-11-14 TUN-6941: Reduce log level to debug when failing to proxy ICMP reply
|
||||
- 2022-11-14 TUN-6935: Cloudflared should use APIToken instead of serviceKey
|
||||
- 2022-11-14 TUN-6935: Cloudflared should use APIToken instead of serviceKey
|
||||
- 2022-11-11 TUN-6937: Bump golang.org/x/* packages to new release tags
|
||||
- 2022-11-10 ZTC-234: macOS tests
|
||||
- 2022-11-09 TUN-6927: Refactor validate access configuration to allow empty audTags only
|
||||
- 2022-11-08 ZTC-234: Replace ICMP funnels when ingress connection changes
|
||||
- 2022-11-04 TUN-6917: Bump go to 1.19.3
|
||||
- 2022-11-02 Issue #574: Better ssh config for short-lived cert (#763)
|
||||
- 2022-10-28 TUN-6898: Fix bug handling IPv6 based ingresses with missing port
|
||||
- 2022-10-28 TUN-6898: Refactor addPortIfMissing
|
||||
2022.10.3
|
||||
- 2022-10-24 TUN-6871: Add default feature to cloudflared to support EOF on QUIC connections
|
||||
- 2022-10-19 TUN-6876: Fix flaky TestTraceICMPRouterEcho by taking account request span can return before reply
|
||||
- 2022-10-18 TUN-6867: Clear spans right after they are serialized to avoid returning duplicate spans
|
||||
2022.10.2
|
||||
- 2022-10-18 TUN-6869: Fix Makefile complaining about missing GO packages
|
||||
- 2022-10-18 TUN-6864: Don't reuse port in quic unit tests
|
||||
- 2022-10-18 TUN-6868: Return left padded tracing ID when tracing identity is converted to string
|
||||
|
||||
2022.10.1
|
||||
- 2022-10-16 TUN-6861: Trace ICMP on Windows
|
||||
- 2022-10-15 TUN-6860: Send access configuration keys to the edge
|
||||
- 2022-10-14 TUN-6858: Trace ICMP reply
|
||||
- 2022-10-13 TUN-6855: Add DatagramV2Type for IP packet with trace and tracing spans
|
||||
- 2022-10-13 TUN-6856: Refactor to lay foundation for tracing ICMP
|
||||
- 2022-10-13 TUN-6604: Trace icmp echo request on Linux and Darwin
|
||||
- 2022-10-12 Fix log message (#591)
|
||||
- 2022-10-12 TUN-6853: Reuse source port when connecting to the edge for quic connections
|
||||
- 2022-10-11 TUN-6829: Allow user of datagramsession to control logging level of errors
|
||||
- 2022-10-10 RTG-2276 Update qtls and go mod tidy
|
||||
- 2022-10-05 Add post-quantum flag to quick tunnel
|
||||
- 2022-10-05 TUN-6823: Update github release message to pull from KV
|
||||
- 2022-10-04 TUN-6825: Fix cloudflared:version images require arch hyphens
|
||||
- 2022-10-03 TUN-6806: Add ingress rule number to log when filtering due to middlware handler
|
||||
- 2022-08-17 Label correct container
|
||||
- 2022-08-16 Fix typo in help text for `cloudflared tunnel route lb`
|
||||
- 2022-07-18 drop usage of cat when sed is invoked to generate the manpage
|
||||
- 2021-03-15 update-build-readme
|
||||
- 2021-03-15 fix link
|
||||
|
||||
2022.10.0
|
||||
- 2022-09-30 TUN-6755: Remove unused publish functions
|
||||
- 2022-09-30 TUN-6813: Only proxy ICMP packets when warp-routing is enabled
|
||||
- 2022-09-29 TUN-6811: Ping group range should be parsed as int32
|
||||
- 2022-09-29 TUN-6812: Drop IP packets if ICMP proxy is not initialized
|
||||
- 2022-09-28 TUN-6716: Document limitation of Windows ICMP proxy
|
||||
- 2022-09-28 TUN-6810: Add component test for post-quantum
|
||||
- 2022-09-27 TUN-6715: Provide suggestion to add cloudflared to ping_group_range if it failed to open ICMP socket
|
||||
- 2022-09-22 TUN-6792: Fix brew core release by not auditing the formula
|
||||
- 2022-09-22 TUN-6774: Validate OriginRequest.Access to add Ingress.Middleware
|
||||
- 2022-09-22 TUN-6775: Add middleware.Handler verification to ProxyHTTP
|
||||
- 2022-09-22 TUN-6791: Calculate ICMPv6 checksum
|
||||
- 2022-09-22 TUN-6801: Add punycode alternatives for ingress rules
|
||||
- 2022-09-21 TUN-6772: Add a JWT Validator as an ingress verifier
|
||||
- 2022-09-21 TUN-6772: Add a JWT Validator as an ingress verifier
|
||||
- 2022-09-21 TUN-6774: Validate OriginRequest.Access to add Ingress.Middleware
|
||||
- 2022-09-21 TUN-6772: Add a JWT Validator as an ingress verifier
|
||||
- 2022-09-20 TUN-6741: ICMP proxy tries to listen on specific IPv4 & IPv6 when possible
|
||||
2022.9.1
|
||||
- 2022-09-20 TUN-6777: Fix race condition in TestFunnelIdleTimeout
|
||||
- 2022-09-20 TUN-6595: Enable datagramv2 and icmp proxy by default
|
||||
- 2022-09-20 TUN-6773: Add access based configuration to ingress.OriginRequestConfig
|
||||
- 2022-09-19 TUN-6778: Cleanup logs about ICMP
|
||||
- 2022-09-19 TUN-6779: cloudflared should also use the root CAs from system pool to validate edge certificate
|
||||
- 2022-09-19 TUN-6780: Add support for certReload to also include support for client certificates
|
||||
- 2022-09-16 TUN-6767: Build ICMP proxy for Windows only when CGO is enabled
|
||||
- 2022-09-15 TUN-6590: Use Windows Teamcity agent to build binary
|
||||
- 2022-09-13 TUN-6592: Decrement TTL and return ICMP time exceed if it's 0
|
||||
- 2022-09-09 TUN-6749: Fix icmp_generic build
|
||||
- 2022-09-09 TUN-6744: On posix platforms, assign unique echo ID per (src, dst, echo ID)
|
||||
- 2022-09-08 TUN-6743: Support ICMPv6 echo on Windows
|
||||
- 2022-09-08 TUN-6689: Utilize new RegisterUDPSession to begin tracing
|
||||
- 2022-09-07 TUN-6688: Update RegisterUdpSession capnproto to include trace context
|
||||
- 2022-09-06 TUN-6740: Detect no UDP packets allowed and fallback from QUIC in that case
|
||||
- 2022-09-06 TUN-6654: Support ICMPv6 on Linux and Darwin
|
||||
- 2022-09-02 TUN-6696: Refactor flow into funnel and close idle funnels
|
||||
- 2022-09-02 TUN-6718: Bump go and go-boring 1.18.6
|
||||
- 2022-08-29 TUN-6531: Implement ICMP proxy for Windows using IcmpSendEcho
|
||||
- 2022-08-24 RTG-1339 Support post-quantum hybrid key exchange
|
||||
|
||||
2022.9.0
|
||||
- 2022-09-05 TUN-6737: Fix datagramV2Type should be declared in its own block so it starts at 0
|
||||
- 2022-09-01 TUN-6725: Fix testProxySSEAllData
|
||||
- 2022-09-01 TUN-6726: Fix maxDatagramPayloadSize for Windows QUIC datagrams
|
||||
- 2022-09-01 TUN-6729: Fix flaky TestClosePreviousProxies
|
||||
- 2022-09-01 TUN-6728: Verify http status code ingress rule
|
||||
- 2022-08-25 TUN-6695: Implement ICMP proxy for linux
|
||||
|
||||
2022.8.4
|
||||
- 2022-08-31 TUN-6717: Update Github action to run with Go 1.19
|
||||
- 2022-08-31 TUN-6720: Remove forcibly closing connection during reconnect signal
|
||||
- 2022-08-29 Release 2022.8.3
|
||||
|
||||
2022.8.3
|
||||
- 2022-08-26 TUN-6708: Fix replace flow logic
|
||||
- 2022-08-25 TUN-6705: Tunnel should retry connections forever
|
||||
- 2022-08-25 TUN-6704: Honor protocol flag when edge discovery is unreachable
|
||||
- 2022-08-25 TUN-6699: Add metric for packet too big dropped
|
||||
- 2022-08-24 TUN-6691: Properly error check for net.ErrClosed
|
||||
- 2022-08-22 TUN-6679: Allow client side of quic request to close body
|
||||
- 2022-08-22 TUN-6586: Change ICMP proxy to only build for Darwin and use echo ID to track flows
|
||||
- 2022-08-18 TUN-6530: Implement ICMPv4 proxy
|
||||
- 2022-08-17 TUN-6666: Define packet package
|
||||
- 2022-08-17 TUN-6667: DatagramMuxerV2 provides a method to receive RawPacket
|
||||
- 2022-08-16 TUN-6657: Ask for Tunnel ID and Configuration on Bug Report
|
||||
- 2022-08-16 TUN-6676: Add suport for trailers in http2 connections
|
||||
- 2022-08-11 TUN-6575: Consume cf-trace-id from incoming http2 TCP requests
|
||||
|
||||
2022.8.2
|
||||
- 2022-08-16 TUN-6656: Docker for arm64 should not be deployed in an amd64 container
|
||||
|
||||
2022.8.1
|
||||
- 2022-08-15 TUN-6617: Updated CHANGES.md for protocol stickiness
|
||||
- 2022-08-12 EDGEPLAT-3918: bump go and go-boring to 1.18.5
|
||||
- 2022-08-12 TUN-6652: Publish dockerfile for both amd64 and arm64
|
||||
- 2022-08-11 TUN-6617: Dont fallback to http2 if QUIC conn was successful.
|
||||
- 2022-08-11 TUN-6617: Dont fallback to http2 if QUIC conn was successful.
|
||||
- 2022-08-11 Revert "TUN-6617: Dont fallback to http2 if QUIC conn was successful."
|
||||
- 2022-08-11 TUN-6617: Dont fallback to http2 if QUIC conn was successful.
|
||||
- 2022-08-01 TUN-6584: Define QUIC datagram v2 format to support proxying IP packets
|
||||
|
||||
2022.8.0
|
||||
- 2022-08-10 TUN-6637: Upgrade quic-go
|
||||
- 2022-08-10 TUN-6646: Add support to SafeStreamCloser to close only write side of stream
|
||||
- 2022-08-09 TUN-6642: Fix unexpected close of quic stream triggered by upstream origin close
|
||||
- 2022-08-09 TUN-6639: Validate cyclic ingress configuration
|
||||
- 2022-08-08 TUN-6637: Upgrade go version and quic-go
|
||||
- 2022-08-08 TUN-6639: Validate cyclic ingress configuration
|
||||
- 2022-08-04 EDGEPLAT-3918: build cloudflared for Bookworm
|
||||
- 2022-08-02 Revert "TUN-6576: Consume cf-trace-id from incoming TCP requests to create root span"
|
||||
- 2022-07-27 TUN-6601: Update gopkg.in/yaml.v3 references in modules
|
||||
- 2022-07-26 TUN-6576: Consume cf-trace-id from incoming TCP requests to create root span
|
||||
- 2022-07-26 TUN-6576: Consume cf-trace-id from incoming TCP requests to create root span
|
||||
- 2022-07-25 TUN-6598: Remove auto assignees on github issues
|
||||
- 2022-07-20 TUN-6583: Remove legacy --ui flag
|
||||
- 2022-07-20 cURL supports stdin and uses os pipes directly without copying
|
||||
- 2022-07-07 TUN-6517: Use QUIC stream context while proxying HTTP requests and TCP connections
|
||||
2022.7.1
|
||||
- 2022-07-06 TUN-6503: Fix transport fallback from QUIC in face of dial error "no network activity"
|
||||
|
||||
2022.7.0
|
||||
- 2022-07-05 TUN-6499: Remove log that is per datagram
|
||||
- 2022-06-24 TUN-6460: Rename metric label location to edge_location
|
||||
- 2022-06-24 TUN-6459: Add cloudflared user-agent to access calls
|
||||
- 2022-06-17 TUN-6427: Differentiate between upstream request closed/canceled and failed origin requests
|
||||
- 2022-06-17 TUN-6388: Fix first tunnel connection not retrying
|
||||
- 2022-06-13 TUN-6384: Correct duplicate connection error to fetch new IP first
|
||||
- 2022-06-13 TUN-6373: Add edge-ip-version to remotely pushed configuration
|
||||
- 2022-06-07 TUN-6010: Add component tests for --edge-ip-version
|
||||
- 2022-05-20 TUN-6007: Implement new edge discovery algorithm
|
||||
- 2022-02-18 Ensure service install directories are created before writing file
|
||||
|
||||
2022.6.3
|
||||
- 2022-06-20 TUN-6362: Add armhf support to cloudflare packaging
|
||||
|
||||
2022.6.2
|
||||
- 2022-06-13 TUN-6381: Write error data on QUIC stream when we fail to talk to the origin; separate logging for protocol errors vs. origin errors.
|
||||
- 2022-06-17 TUN-6414: Remove go-sumtype from cloudflared build process
|
||||
- 2022-06-01 Add Http2Origin option to force HTTP/2 origin connections
|
||||
- 2022-06-02 fix ingress rules unit test
|
||||
- 2022-06-09 Update remaining OriginRequestConfig functions for Http2Origins
|
||||
- 2022-05-31 Add image source label to docker container.
|
||||
- 2022-05-10 Warp Private Network link updated
|
||||
|
||||
2022.6.1
|
||||
- 2022-06-14 TUN-6395: Fix writing RPM repo data
|
||||
|
||||
2022.6.0
|
||||
- 2022-06-14 Revert "TUN-6010: Add component tests for --edge-ip-version"
|
||||
- 2022-06-14 Revert "TUN-6373: Add edge-ip-version to remotely pushed configuration"
|
||||
- 2022-06-14 Revert "TUN-6384: Correct duplicate connection error to fetch new IP first"
|
||||
- 2022-06-14 Revert "TUN-6007: Implement new edge discovery algorithm"
|
||||
- 2022-06-13 TUN-6385: Don't share err between acceptStream loop and per-stream goroutines
|
||||
- 2022-06-13 TUN-6384: Correct duplicate connection error to fetch new IP first
|
||||
- 2022-06-13 TUN-6373: Add edge-ip-version to remotely pushed configuration
|
||||
- 2022-06-13 TUN-6380: Enforce connect and keep-alive timeouts for TCP connections in both WARP routing and websocket based TCP proxy.
|
||||
- 2022-06-11 Update issue templates
|
||||
- 2022-06-11 Amendment to previous PR
|
||||
- 2022-06-09 TUN-6347: Add TCP stream logs with FlowID
|
||||
- 2022-06-08 TUN-6361: Add cloudflared arm builds to pkging as well
|
||||
- 2022-06-07 TUN-6357: Add connector id to ready check endpoint
|
||||
- 2022-06-07 TUN-6010: Add component tests for --edge-ip-version
|
||||
- 2022-06-06 TUN-6191: Update quic-go to v0.27.1 and with custom patch to allow keep alive period to be configurable
|
||||
- 2022-06-03 TUN-6343: Fix QUIC->HTTP2 fallback
|
||||
- 2022-06-02 TUN-6339: Add config for IPv6 support
|
||||
- 2022-06-02 TUN-6341: Fix default config value for edge-ip-version
|
||||
- 2022-06-01 TUN-6323: Add Xenial and Trusty for Ubuntu pkging
|
||||
- 2022-05-31 TUN-6210: Add cloudflared.repo to make it easy for yum installs
|
||||
- 2022-05-30 TUN-6293: Update yaml v3 to latest hotfix
|
||||
- 2022-05-20 TUN-6007: Implement new edge discovery algorithm
|
||||
2022.5.3
|
||||
- 2022-05-30 TUN-6308: Add debug logs to see if packets are sent/received from edge
|
||||
- 2022-05-30 TUN-6301: Allow to update logger used by UDP session manager
|
||||
|
||||
2022.5.2
|
||||
- 2022-05-23 TUN-6270: Import gpg keys from environment variables
|
||||
- 2022-05-24 TUN-6209: Improve feedback process if release_pkgs to deb and rpm fail
|
||||
- 2022-05-24 TUN-6280: Don't wrap qlog connection tracer for gatethering QUIC metrics since we're not writing qlog files.
|
||||
- 2022-05-25 TUN-6209: Sign RPM packages
|
||||
- 2022-05-25 TUN-6285: Upload pkg assets to repos when cloudflared is released.
|
||||
- 2022-05-24 TUN-6282: Upgrade golang to 1.17.10, go-boring to 1.17.9
|
||||
- 2022-05-26 TUN-6292: Debug builds for cloudflared
|
||||
- 2022-05-28 TUN-6304: Fixed some file permission issues
|
||||
- 2022-05-11 TUN-6197: Publish to brew core should not try to open the browser
|
||||
- 2022-05-12 TUN-5943: Add RPM support
|
||||
- 2022-05-18 TUN-6248: Fix panic in cloudflared during tracing when origin doesn't provide header map
|
||||
- 2022-05-18 TUN-6250: Add upstream response status code to tracing span attributes
|
||||
|
||||
2022.5.1
|
||||
- 2022-05-06 TUN-6146: Release_pkgs is now a generic command line script
|
||||
- 2022-05-06 TUN-6185: Fix tcpOverWSOriginService not using original scheme for String representation
|
||||
- 2022-05-05 TUN-6175: Simply debian packaging by structural upload
|
||||
- 2022-05-05 TUN-5945: Added support for Ubuntu releases
|
||||
- 2022-05-04 TUN-6054: Create and upload deb packages to R2
|
||||
- 2022-05-03 TUN-6161: Set git user/email for brew core release
|
||||
- 2022-05-03 TUN-6166: Fix mocked QUIC transport for UDP proxy manager to return expected error
|
||||
- 2022-04-27 TUN-6016: Push local managed tunnels configuration to the edge
|
||||
2022.5.0
|
||||
- 2022-05-02 TUN-6158: Update golang.org/x/crypto
|
||||
- 2022-04-20 VULN-8383 Bump yaml.v2 to yaml.v3
|
||||
- 2022-04-21 TUN-6123: For a given connection with edge, close all datagram sessions through this connection when it's closed
|
||||
- 2022-04-20 TUN-6015: Add RPC method for pushing local config
|
||||
- 2022-04-21 TUN-6130: Fix vendoring due to case sensitive typo in package
|
||||
- 2022-04-27 TUN-6142: Add tunnel details support to RPC
|
||||
- 2022-04-28 TUN-6014: Add remote config flag as default feature
|
||||
- 2022-04-12 TUN-6000: Another fix for publishing to brew core
|
||||
- 2022-04-11 TUN-5990: Add otlp span export to response header
|
||||
- 2022-04-19 TUN-6070: First connection retries other edge IPs if the error is quic timeout(likely due to firewall blocking UDP)
|
||||
- 2022-04-11 TUN-6030: Add ttfb span for origin http request
|
||||
|
||||
2022.4.1
|
||||
- 2022-04-11 TUN-6035: Reduce buffer size when proxying data
|
||||
- 2022-04-11 TUN-6038: Reduce buffer size used for proxying data
|
||||
|
||||
+9
-10
@@ -7,20 +7,19 @@ export CGO_ENABLED=0
|
||||
# This controls the directory the built artifacts go into
|
||||
export ARTIFACT_DIR=built_artifacts/
|
||||
mkdir -p $ARTIFACT_DIR
|
||||
windowsArchs=("amd64" "386")
|
||||
export TARGET_OS=windows
|
||||
for arch in ${windowsArchs[@]}; do
|
||||
export TARGET_ARCH=$arch
|
||||
make cloudflared-msi
|
||||
mv ./cloudflared.exe $ARTIFACT_DIR/cloudflared-windows-$arch.exe
|
||||
mv cloudflared-$VERSION-$arch.msi $ARTIFACT_DIR/cloudflared-windows-$arch.msi
|
||||
done
|
||||
|
||||
|
||||
linuxArchs=("386" "amd64" "arm" "arm64")
|
||||
linuxArchs=("386" "amd64" "arm" "armhf" "arm64")
|
||||
export TARGET_OS=linux
|
||||
for arch in ${linuxArchs[@]}; do
|
||||
unset TARGET_ARM
|
||||
export TARGET_ARCH=$arch
|
||||
|
||||
## Support for armhf builds
|
||||
if [[ $arch == armhf ]] ; then
|
||||
export TARGET_ARCH=arm
|
||||
export TARGET_ARM=7
|
||||
fi
|
||||
|
||||
make cloudflared-deb
|
||||
mv cloudflared\_$VERSION\_$arch.deb $ARTIFACT_DIR/cloudflared-linux-$arch.deb
|
||||
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
//Package carrier provides a WebSocket proxy to carry or proxy a connection
|
||||
//from the local client to the edge. See it as a wrapper around any protocol
|
||||
//that it packages up in a WebSocket connection to the edge.
|
||||
// Package carrier provides a WebSocket proxy to carry or proxy a connection
|
||||
// from the local client to the edge. See it as a wrapper around any protocol
|
||||
// that it packages up in a WebSocket connection to the edge.
|
||||
package carrier
|
||||
|
||||
import (
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/stream"
|
||||
"github.com/cloudflare/cloudflared/token"
|
||||
cfwebsocket "github.com/cloudflare/cloudflared/websocket"
|
||||
)
|
||||
@@ -37,7 +38,7 @@ func (ws *Websocket) ServeStream(options *StartOptions, conn io.ReadWriter) erro
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
cfwebsocket.Stream(wsConn, conn, ws.log)
|
||||
stream.Pipe(wsConn, conn, ws.log)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+13
-49
@@ -1,25 +1,21 @@
|
||||
package certutil
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type namedTunnelToken struct {
|
||||
ZoneID string `json:"zoneID"`
|
||||
AccountID string `json:"accountID"`
|
||||
ServiceKey string `json:"serviceKey"`
|
||||
ZoneID string `json:"zoneID"`
|
||||
AccountID string `json:"accountID"`
|
||||
APIToken string `json:"apiToken"`
|
||||
}
|
||||
|
||||
type OriginCert struct {
|
||||
PrivateKey interface{}
|
||||
Cert *x509.Certificate
|
||||
ZoneID string
|
||||
ServiceKey string
|
||||
AccountID string
|
||||
ZoneID string
|
||||
APIToken string
|
||||
AccountID string
|
||||
}
|
||||
|
||||
func DecodeOriginCert(blocks []byte) (*OriginCert, error) {
|
||||
@@ -33,29 +29,11 @@ func DecodeOriginCert(blocks []byte) (*OriginCert, error) {
|
||||
break
|
||||
}
|
||||
switch block.Type {
|
||||
case "PRIVATE KEY":
|
||||
if originCert.PrivateKey != nil {
|
||||
return nil, fmt.Errorf("Found multiple private key in the certificate")
|
||||
}
|
||||
// RSA private key
|
||||
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot parse private key")
|
||||
}
|
||||
originCert.PrivateKey = privateKey
|
||||
case "CERTIFICATE":
|
||||
if originCert.Cert != nil {
|
||||
return nil, fmt.Errorf("Found multiple certificates in the certificate")
|
||||
}
|
||||
cert, err := x509.ParseCertificates(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot parse certificate")
|
||||
} else if len(cert) > 1 {
|
||||
return nil, fmt.Errorf("Found multiple certificates in the certificate")
|
||||
}
|
||||
originCert.Cert = cert[0]
|
||||
case "WARP TOKEN", "ARGO TUNNEL TOKEN":
|
||||
if originCert.ZoneID != "" || originCert.ServiceKey != "" {
|
||||
case "PRIVATE KEY", "CERTIFICATE":
|
||||
// this is for legacy purposes.
|
||||
break
|
||||
case "ARGO TUNNEL TOKEN":
|
||||
if originCert.ZoneID != "" || originCert.APIToken != "" {
|
||||
return nil, fmt.Errorf("Found multiple tokens in the certificate")
|
||||
}
|
||||
// The token is a string,
|
||||
@@ -63,18 +41,8 @@ func DecodeOriginCert(blocks []byte) (*OriginCert, error) {
|
||||
ntt := namedTunnelToken{}
|
||||
if err := json.Unmarshal(block.Bytes, &ntt); err == nil {
|
||||
originCert.ZoneID = ntt.ZoneID
|
||||
originCert.ServiceKey = ntt.ServiceKey
|
||||
originCert.APIToken = ntt.APIToken
|
||||
originCert.AccountID = ntt.AccountID
|
||||
} else {
|
||||
// Try the older format, where the zoneID and service key are separated by
|
||||
// a new line character
|
||||
token := string(block.Bytes)
|
||||
s := strings.Split(token, "\n")
|
||||
if len(s) != 2 {
|
||||
return nil, fmt.Errorf("Cannot parse token")
|
||||
}
|
||||
originCert.ZoneID = s[0]
|
||||
originCert.ServiceKey = s[1]
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown block %s in the certificate", block.Type)
|
||||
@@ -82,11 +50,7 @@ func DecodeOriginCert(blocks []byte) (*OriginCert, error) {
|
||||
block, rest = pem.Decode(rest)
|
||||
}
|
||||
|
||||
if originCert.PrivateKey == nil {
|
||||
return nil, fmt.Errorf("Missing private key in the certificate")
|
||||
} else if originCert.Cert == nil {
|
||||
return nil, fmt.Errorf("Missing certificate in the certificate")
|
||||
} else if originCert.ZoneID == "" || originCert.ServiceKey == "" {
|
||||
if originCert.ZoneID == "" || originCert.APIToken == "" {
|
||||
return nil, fmt.Errorf("Missing token in the certificate")
|
||||
}
|
||||
|
||||
|
||||
+12
-28
@@ -13,49 +13,33 @@ func TestLoadOriginCert(t *testing.T) {
|
||||
assert.Equal(t, fmt.Errorf("Cannot decode empty certificate"), err)
|
||||
assert.Nil(t, cert)
|
||||
|
||||
blocks, err := ioutil.ReadFile("test-cert-no-key.pem")
|
||||
assert.Nil(t, err)
|
||||
cert, err = DecodeOriginCert(blocks)
|
||||
assert.Equal(t, fmt.Errorf("Missing private key in the certificate"), err)
|
||||
assert.Nil(t, cert)
|
||||
|
||||
blocks, err = ioutil.ReadFile("test-cert-two-certificates.pem")
|
||||
assert.Nil(t, err)
|
||||
cert, err = DecodeOriginCert(blocks)
|
||||
assert.Equal(t, fmt.Errorf("Found multiple certificates in the certificate"), err)
|
||||
assert.Nil(t, cert)
|
||||
|
||||
blocks, err = ioutil.ReadFile("test-cert-unknown-block.pem")
|
||||
blocks, err := ioutil.ReadFile("test-cert-unknown-block.pem")
|
||||
assert.Nil(t, err)
|
||||
cert, err = DecodeOriginCert(blocks)
|
||||
assert.Equal(t, fmt.Errorf("Unknown block RSA PRIVATE KEY in the certificate"), err)
|
||||
assert.Nil(t, cert)
|
||||
|
||||
blocks, err = ioutil.ReadFile("test-cert.pem")
|
||||
assert.Nil(t, err)
|
||||
cert, err = DecodeOriginCert(blocks)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cert)
|
||||
assert.Equal(t, "7b0a4d77dfb881c1a3b7d61ea9443e19", cert.ZoneID)
|
||||
key := "v1.0-58bd4f9e28f7b3c28e05a35ff3e80ab4fd9644ef3fece537eb0d12e2e9258217-183442fbb0bbdb3e571558fec9b5589ebd77aafc87498ee3f09f64a4ad79ffe8791edbae08b36c1d8f1d70a8670de56922dff92b15d214a524f4ebfa1958859e-7ce80f79921312a6022c5d25e2d380f82ceaefe3fbdc43dd13b080e3ef1e26f7"
|
||||
assert.Equal(t, key, cert.ServiceKey)
|
||||
}
|
||||
|
||||
func TestNewlineArgoTunnelToken(t *testing.T) {
|
||||
ArgoTunnelTokenTest(t, "test-argo-tunnel-cert.pem")
|
||||
func TestJSONArgoTunnelTokenEmpty(t *testing.T) {
|
||||
cert, err := DecodeOriginCert([]byte{})
|
||||
blocks, err := ioutil.ReadFile("test-cert-no-token.pem")
|
||||
assert.Nil(t, err)
|
||||
cert, err = DecodeOriginCert(blocks)
|
||||
assert.Equal(t, fmt.Errorf("Missing token in the certificate"), err)
|
||||
assert.Nil(t, cert)
|
||||
}
|
||||
|
||||
func TestJSONArgoTunnelToken(t *testing.T) {
|
||||
// The given cert's Argo Tunnel Token was generated by base64 encoding this JSON:
|
||||
// {
|
||||
// "zoneID": "7b0a4d77dfb881c1a3b7d61ea9443e19",
|
||||
// "serviceKey": "test-service-key",
|
||||
// "apiToken": "test-service-key",
|
||||
// "accountID": "abcdabcdabcdabcd1234567890abcdef"
|
||||
// }
|
||||
ArgoTunnelTokenTest(t, "test-argo-tunnel-cert-json.pem")
|
||||
CloudflareTunnelTokenTest(t, "test-cloudflare-tunnel-cert-json.pem")
|
||||
}
|
||||
|
||||
func ArgoTunnelTokenTest(t *testing.T, path string) {
|
||||
func CloudflareTunnelTokenTest(t *testing.T, path string) {
|
||||
blocks, err := ioutil.ReadFile(path)
|
||||
assert.Nil(t, err)
|
||||
cert, err := DecodeOriginCert(blocks)
|
||||
@@ -63,5 +47,5 @@ func ArgoTunnelTokenTest(t *testing.T, path string) {
|
||||
assert.NotNil(t, cert)
|
||||
assert.Equal(t, "7b0a4d77dfb881c1a3b7d61ea9443e19", cert.ZoneID)
|
||||
key := "test-service-key"
|
||||
assert.Equal(t, key, cert.ServiceKey)
|
||||
assert.Equal(t, key, cert.APIToken)
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
|
||||
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
|
||||
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
|
||||
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
|
||||
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
|
||||
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
|
||||
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
|
||||
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
|
||||
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
|
||||
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
|
||||
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
|
||||
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
|
||||
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
|
||||
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
|
||||
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
|
||||
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
|
||||
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
|
||||
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
|
||||
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
|
||||
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN WARP TOKEN-----
|
||||
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4
|
||||
ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5
|
||||
MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4
|
||||
NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2
|
||||
NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5
|
||||
OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz
|
||||
ZWYxZTI2Zjc=
|
||||
-----END WARP TOKEN-----
|
||||
@@ -51,7 +51,6 @@ K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN ARGO TUNNEL TOKEN-----
|
||||
eyJ6b25lSUQiOiAiN2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkiLCAi
|
||||
c2VydmljZUtleSI6ICJ0ZXN0LXNlcnZpY2Uta2V5IiwgImFjY291bnRJRCI6ICJh
|
||||
YmNkYWJjZGFiY2RhYmNkMTIzNDU2Nzg5MGFiY2RlZiJ9
|
||||
eyJ6b25lSUQiOiAiN2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkiLCAiYWNjb3VudElE
|
||||
IjogImFiY2RhYmNkYWJjZGFiY2QxMjM0NTY3ODkwYWJjZGVmIn0=
|
||||
-----END ARGO TUNNEL TOKEN-----
|
||||
@@ -1,85 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
|
||||
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
|
||||
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
|
||||
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
|
||||
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
|
||||
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
|
||||
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
|
||||
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
|
||||
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
|
||||
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
|
||||
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
|
||||
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
|
||||
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
|
||||
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
|
||||
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
|
||||
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
|
||||
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
|
||||
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
|
||||
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
|
||||
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
|
||||
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
|
||||
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
|
||||
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
|
||||
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
|
||||
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
|
||||
uQicoQq3yzeQh20wtrtaXzTNmA==
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
|
||||
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
|
||||
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
|
||||
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
|
||||
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
|
||||
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
|
||||
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
|
||||
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
|
||||
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
|
||||
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
|
||||
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
|
||||
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
|
||||
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
|
||||
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
|
||||
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
|
||||
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
|
||||
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
|
||||
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
|
||||
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
|
||||
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
|
||||
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
|
||||
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
|
||||
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
|
||||
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
|
||||
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
|
||||
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
|
||||
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
|
||||
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
|
||||
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
|
||||
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
|
||||
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
|
||||
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
|
||||
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
|
||||
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
|
||||
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
|
||||
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
|
||||
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
|
||||
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
|
||||
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN WARP TOKEN-----
|
||||
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4
|
||||
ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5
|
||||
MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4
|
||||
NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2
|
||||
NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5
|
||||
OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz
|
||||
ZWYxZTI2Zjc=
|
||||
-----END WARP TOKEN-----
|
||||
@@ -50,7 +50,7 @@ cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
|
||||
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN WARP TOKEN-----
|
||||
-----BEGIN ARGO TUNNEL TOKEN-----
|
||||
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4
|
||||
ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5
|
||||
MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4
|
||||
@@ -58,7 +58,7 @@ NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2
|
||||
NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5
|
||||
OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz
|
||||
ZWYxZTI2Zjc=
|
||||
-----END WARP TOKEN-----
|
||||
-----END ARGO TUNNEL TOKEN-----
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
|
||||
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
|
||||
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
|
||||
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
|
||||
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
|
||||
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
|
||||
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
|
||||
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
|
||||
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
|
||||
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
|
||||
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
|
||||
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
|
||||
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
|
||||
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
|
||||
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
|
||||
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
|
||||
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
|
||||
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
|
||||
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
|
||||
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
|
||||
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
|
||||
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
|
||||
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
|
||||
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
|
||||
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
|
||||
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
|
||||
uQicoQq3yzeQh20wtrtaXzTNmA==
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
|
||||
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
|
||||
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
|
||||
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
|
||||
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
|
||||
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
|
||||
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
|
||||
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
|
||||
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
|
||||
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
|
||||
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
|
||||
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
|
||||
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
|
||||
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
|
||||
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
|
||||
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
|
||||
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
|
||||
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
|
||||
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
|
||||
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN WARP TOKEN-----
|
||||
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4
|
||||
ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5
|
||||
MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4
|
||||
NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2
|
||||
NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5
|
||||
OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz
|
||||
ZWYxZTI2Zjc=
|
||||
-----END WARP TOKEN-----
|
||||
@@ -51,6 +51,7 @@ K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN ARGO TUNNEL TOKEN-----
|
||||
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdGVzdC1zZXJ2aWNlLWtl
|
||||
eQ==
|
||||
eyJ6b25lSUQiOiAiN2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkiLCAiYXBpVG9rZW4i
|
||||
OiAidGVzdC1zZXJ2aWNlLWtleSIsICJhY2NvdW50SUQiOiAiYWJjZGFiY2RhYmNkYWJjZDEyMzQ1
|
||||
Njc4OTBhYmNkZWYifQ==
|
||||
-----END ARGO TUNNEL TOKEN-----
|
||||
@@ -104,7 +104,7 @@ func (r *RESTClient) sendRequest(method string, url url.URL, body interface{}) (
|
||||
if bodyReader != nil {
|
||||
req.Header.Set("Content-Type", jsonContentType)
|
||||
}
|
||||
req.Header.Add("X-Auth-User-Service-Key", r.authToken)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.authToken))
|
||||
req.Header.Add("Accept", "application/json;version=1")
|
||||
return r.client.Do(req)
|
||||
}
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ type IPRouteClient interface {
|
||||
type VnetClient interface {
|
||||
CreateVirtualNetwork(newVnet NewVirtualNetwork) (VirtualNetwork, error)
|
||||
ListVirtualNetworks(filter *VnetFilter) ([]*VirtualNetwork, error)
|
||||
DeleteVirtualNetwork(id uuid.UUID) error
|
||||
DeleteVirtualNetwork(id uuid.UUID, force bool) error
|
||||
UpdateVirtualNetwork(id uuid.UUID, updates UpdateVirtualNetwork) error
|
||||
}
|
||||
|
||||
|
||||
@@ -80,9 +80,16 @@ func (r *RESTClient) ListVirtualNetworks(filter *VnetFilter) ([]*VirtualNetwork,
|
||||
return nil, r.statusCodeToError("list virtual networks", resp)
|
||||
}
|
||||
|
||||
func (r *RESTClient) DeleteVirtualNetwork(id uuid.UUID) error {
|
||||
func (r *RESTClient) DeleteVirtualNetwork(id uuid.UUID, force bool) error {
|
||||
endpoint := r.baseEndpoints.accountVnets
|
||||
endpoint.Path = path.Join(endpoint.Path, url.PathEscape(id.String()))
|
||||
|
||||
queryParams := url.Values{}
|
||||
if force {
|
||||
queryParams.Set("force", strconv.FormatBool(force))
|
||||
}
|
||||
endpoint.RawQuery = queryParams.Encode()
|
||||
|
||||
resp, err := r.sendRequest("DELETE", endpoint, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "REST request failed")
|
||||
|
||||
+75
-60
@@ -1,23 +1,29 @@
|
||||
pinned_go: &pinned_go go=1.17.5-1
|
||||
pinned_go_fips: &pinned_go_fips go-boring=1.17.5-1
|
||||
pinned_go: &pinned_go go=1.19.3-1
|
||||
pinned_go_fips: &pinned_go_fips go-boring=1.19.3-1
|
||||
|
||||
build_dir: &build_dir /cfsetup_build
|
||||
default-flavor: buster
|
||||
default-flavor: bullseye
|
||||
stretch: &stretch
|
||||
build:
|
||||
build_dir: *build_dir
|
||||
builddeps:
|
||||
builddeps: &build_deps
|
||||
- *pinned_go
|
||||
- build-essential
|
||||
- gotest-to-teamcity
|
||||
pre-cache: &build_pre_cache
|
||||
- export GOCACHE=/cfsetup_build/.cache/go-build
|
||||
- go install golang.org/x/tools/cmd/goimports@latest
|
||||
post-cache:
|
||||
- export GOOS=linux
|
||||
- export GOARCH=amd64
|
||||
- make cloudflared
|
||||
build-fips:
|
||||
build_dir: *build_dir
|
||||
builddeps:
|
||||
builddeps: &build_deps_fips
|
||||
- *pinned_go_fips
|
||||
- build-essential
|
||||
- gotest-to-teamcity
|
||||
pre-cache: *build_pre_cache
|
||||
post-cache:
|
||||
- export GOOS=linux
|
||||
- export GOARCH=amd64
|
||||
@@ -40,16 +46,22 @@ stretch: &stretch
|
||||
- libffi-dev
|
||||
- python3-setuptools
|
||||
- python3-pip
|
||||
- reprepro
|
||||
- createrepo
|
||||
pre-cache: &github_release_pkgs_pre_cache
|
||||
- wget https://github.com/sudarshan-reddy/msitools/releases/download/v0.101b/wixl -P /usr/local/bin
|
||||
- chmod a+x /usr/local/bin/wixl
|
||||
- pip3 install pynacl==1.4.0
|
||||
- pip3 install pygithub==1.55
|
||||
- pip3 install boto3==1.22.9
|
||||
- pip3 install python-gnupg==0.4.9
|
||||
post-cache:
|
||||
# build all packages (except macos and FIPS) and move them to /cfsetup/built_artifacts
|
||||
- ./build-packages.sh
|
||||
# release the packages built and moved to /cfsetup/built_artifacts
|
||||
- make github-release-built-pkgs
|
||||
# publish packages to linux repos
|
||||
- make release-pkgs-linux
|
||||
# handle FIPS separately so that we built with gofips compiler
|
||||
github-fips-release-pkgs:
|
||||
build_dir: *build_dir
|
||||
@@ -72,6 +84,13 @@ stretch: &stretch
|
||||
# same logic as above, but for FIPS packages only
|
||||
- ./build-packages-fips.sh
|
||||
- make github-release-built-pkgs
|
||||
generate-versions-file:
|
||||
build_dir: *build_dir
|
||||
builddeps:
|
||||
- *pinned_go
|
||||
- build-essential
|
||||
post-cache:
|
||||
- make generate-docker-version
|
||||
build-deb:
|
||||
build_dir: *build_dir
|
||||
builddeps: &build_deb_deps
|
||||
@@ -113,21 +132,9 @@ stretch: &stretch
|
||||
- export GOOS=linux
|
||||
- export GOARCH=arm64
|
||||
- make cloudflared-deb
|
||||
publish-deb:
|
||||
build_dir: *build_dir
|
||||
builddeps:
|
||||
- *pinned_go
|
||||
- build-essential
|
||||
- fakeroot
|
||||
- rubygem-fpm
|
||||
- openssh-client
|
||||
post-cache:
|
||||
- export GOOS=linux
|
||||
- export GOARCH=amd64
|
||||
- make publish-deb
|
||||
github-release-macos-amd64:
|
||||
build_dir: *build_dir
|
||||
builddeps:
|
||||
builddeps: &build_pygithub
|
||||
- *pinned_go
|
||||
- build-essential
|
||||
- python3-dev
|
||||
@@ -139,15 +146,31 @@ stretch: &stretch
|
||||
- pip3 install pygithub==1.55
|
||||
post-cache:
|
||||
- make github-mac-upload
|
||||
test:
|
||||
github-release-windows:
|
||||
build_dir: *build_dir
|
||||
builddeps:
|
||||
- *pinned_go
|
||||
- build-essential
|
||||
- gotest-to-teamcity
|
||||
pre-cache: &test_pre_cache
|
||||
- go get golang.org/x/tools/cmd/goimports
|
||||
- go get github.com/sudarshan-reddy/go-sumtype@v0.0.0-20210827105221-82eca7e5abb1
|
||||
- python3-dev
|
||||
- libffi-dev
|
||||
- python3-setuptools
|
||||
- python3-pip
|
||||
- wget
|
||||
# libmsi and libgcab are libraries the wixl binary depends on.
|
||||
- libmsi-dev
|
||||
- libgcab-dev
|
||||
pre-cache:
|
||||
- wget https://github.com/sudarshan-reddy/msitools/releases/download/v0.101b/wixl -P /usr/local/bin
|
||||
- chmod a+x /usr/local/bin/wixl
|
||||
- pip3 install pynacl==1.4.0
|
||||
- pip3 install pygithub==1.55
|
||||
post-cache:
|
||||
- .teamcity/package-windows.sh
|
||||
- make github-windows-upload
|
||||
test:
|
||||
build_dir: *build_dir
|
||||
builddeps: *build_deps
|
||||
pre-cache: *build_pre_cache
|
||||
post-cache:
|
||||
- export GOOS=linux
|
||||
- export GOARCH=amd64
|
||||
@@ -156,11 +179,8 @@ stretch: &stretch
|
||||
- make test | gotest-to-teamcity
|
||||
test-fips:
|
||||
build_dir: *build_dir
|
||||
builddeps:
|
||||
- *pinned_go_fips
|
||||
- build-essential
|
||||
- gotest-to-teamcity
|
||||
pre-cache: *test_pre_cache
|
||||
builddeps: *build_deps_fips
|
||||
pre-cache: *build_pre_cache
|
||||
post-cache:
|
||||
- export GOOS=linux
|
||||
- export GOARCH=amd64
|
||||
@@ -169,6 +189,26 @@ stretch: &stretch
|
||||
- ./fmt-check.sh
|
||||
- make test | gotest-to-teamcity
|
||||
component-test:
|
||||
build_dir: *build_dir
|
||||
builddeps:
|
||||
- *pinned_go
|
||||
- python3.7
|
||||
- python3-pip
|
||||
- python3-setuptools
|
||||
# procps installs the ps command which is needed in test_sysv_service because the init script
|
||||
# uses ps pid to determine if the agent is running
|
||||
- procps
|
||||
pre-cache-copy-paths:
|
||||
- component-tests/requirements.txt
|
||||
pre-cache: &component_test_pre_cache
|
||||
- sudo pip3 install --upgrade -r component-tests/requirements.txt
|
||||
post-cache: &component_test_post_cache
|
||||
# Creates and routes a Named Tunnel for this build. Also constructs config file from env vars.
|
||||
- python3 component-tests/setup.py --type create
|
||||
- pytest component-tests -o log_cli=true --log-cli-level=INFO
|
||||
# The Named Tunnel is deleted and its route unprovisioned here.
|
||||
- python3 component-tests/setup.py --type cleanup
|
||||
component-test-fips:
|
||||
build_dir: *build_dir
|
||||
builddeps:
|
||||
- *pinned_go_fips
|
||||
@@ -180,32 +220,21 @@ stretch: &stretch
|
||||
- procps
|
||||
pre-cache-copy-paths:
|
||||
- component-tests/requirements.txt
|
||||
pre-cache:
|
||||
- sudo pip3 install --upgrade -r component-tests/requirements.txt
|
||||
post-cache:
|
||||
# Creates and routes a Named Tunnel for this build. Also constructs config file from env vars.
|
||||
- python3 component-tests/setup.py --type create
|
||||
- pytest component-tests -o log_cli=true --log-cli-level=INFO
|
||||
# The Named Tunnel is deleted and its route unprovisioned here.
|
||||
- python3 component-tests/setup.py --type cleanup
|
||||
pre-cache: *component_test_pre_cache
|
||||
post-cache: *component_test_post_cache
|
||||
update-homebrew:
|
||||
builddeps:
|
||||
- openssh-client
|
||||
- s3cmd
|
||||
- jq
|
||||
- build-essential
|
||||
- procps
|
||||
post-cache:
|
||||
- .teamcity/update-homebrew.sh
|
||||
- .teamcity/update-homebrew-core.sh
|
||||
github-message-release:
|
||||
build_dir: *build_dir
|
||||
builddeps:
|
||||
- *pinned_go
|
||||
- build-essential
|
||||
- python3-dev
|
||||
- libffi-dev
|
||||
- python3-setuptools
|
||||
- python3-pip
|
||||
builddeps: *build_pygithub
|
||||
pre-cache: *install_pygithub
|
||||
post-cache:
|
||||
- make github-message
|
||||
@@ -216,13 +245,13 @@ stretch: &stretch
|
||||
- build-essential
|
||||
- python3
|
||||
- genisoimage
|
||||
- jetez
|
||||
pre-cache:
|
||||
- ln -s /usr/bin/genisoimage /usr/bin/mkisofs
|
||||
post-cache:
|
||||
- export CGO_ENABLED=0
|
||||
- export GOOS=freebsd
|
||||
- export GOARCH=amd64
|
||||
- make cloudflared-junos
|
||||
- make cloudflared
|
||||
publish-junos:
|
||||
build_dir: *build_dir
|
||||
builddeps:
|
||||
@@ -241,18 +270,4 @@ stretch: &stretch
|
||||
|
||||
buster: *stretch
|
||||
bullseye: *stretch
|
||||
centos-7:
|
||||
publish-rpm:
|
||||
build_dir: *build_dir
|
||||
builddeps: &el7_builddeps
|
||||
- https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
|
||||
pre-cache:
|
||||
- yum install -y fakeroot
|
||||
- yum upgrade -y binutils-2.27-44.base.el7.x86_64
|
||||
- wget https://go.dev/dl/go1.17.5.linux-amd64.tar.gz -P /tmp/
|
||||
- tar -C /usr/local -xzf /tmp/go1.17.5.linux-amd64.tar.gz
|
||||
post-cache:
|
||||
- export PATH=$PATH:/usr/local/go/bin
|
||||
- export GOOS=linux
|
||||
- export GOARCH=amd64
|
||||
- make publish-rpm
|
||||
bookworm: *stretch
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -37,16 +37,13 @@ const (
|
||||
sshConfigTemplate = `
|
||||
Add to your {{.Home}}/.ssh/config:
|
||||
|
||||
Host {{.Hostname}}
|
||||
{{- if .ShortLivedCerts}}
|
||||
ProxyCommand bash -c '{{.Cloudflared}} access ssh-gen --hostname %h; ssh -tt %r@cfpipe-{{.Hostname}} >&2 <&1'
|
||||
|
||||
Host cfpipe-{{.Hostname}}
|
||||
HostName {{.Hostname}}
|
||||
Match host {{.Hostname}} exec "{{.Cloudflared}} access ssh-gen --hostname %h"
|
||||
ProxyCommand {{.Cloudflared}} access ssh --hostname %h
|
||||
IdentityFile ~/.cloudflared/{{.Hostname}}-cf_key
|
||||
CertificateFile ~/.cloudflared/{{.Hostname}}-cf_key-cert.pub
|
||||
IdentityFile ~/.cloudflared/%h-cf_key
|
||||
CertificateFile ~/.cloudflared/%h-cf_key-cert.pub
|
||||
{{- else}}
|
||||
Host {{.Hostname}}
|
||||
ProxyCommand {{.Cloudflared}} access ssh --hostname %h
|
||||
{{end}}
|
||||
`
|
||||
@@ -56,11 +53,13 @@ const sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b@sentry.io/189878"
|
||||
|
||||
var (
|
||||
shutdownC chan struct{}
|
||||
userAgent = "DEV"
|
||||
)
|
||||
|
||||
// Init will initialize and store vars from the main program
|
||||
func Init(shutdown chan struct{}) {
|
||||
func Init(shutdown chan struct{}, version string) {
|
||||
shutdownC = shutdown
|
||||
userAgent = fmt.Sprintf("cloudflared/%s", version)
|
||||
}
|
||||
|
||||
// Flags return the global flags for Access related commands (hopefully none)
|
||||
@@ -203,7 +202,11 @@ func Commands() []*cli.Command {
|
||||
|
||||
// login pops up the browser window to do the actual login and JWT generation
|
||||
func login(c *cli.Context) error {
|
||||
if err := raven.SetDSN(sentryDSN); err != nil {
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: sentryDSN,
|
||||
Release: c.App.Version,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -252,7 +255,11 @@ func ensureURLScheme(url string) string {
|
||||
|
||||
// curl provides a wrapper around curl, passing Access JWT along in request
|
||||
func curl(c *cli.Context) error {
|
||||
if err := raven.SetDSN(sentryDSN); err != nil {
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: sentryDSN,
|
||||
Release: c.App.Version,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
|
||||
@@ -294,6 +301,7 @@ func curl(c *cli.Context) error {
|
||||
// run kicks off a shell task and pipe the results to the respective std pipes
|
||||
func run(cmd string, args ...string) error {
|
||||
c := exec.Command(cmd, args...)
|
||||
c.Stdin = os.Stdin
|
||||
stderr, err := c.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -314,7 +322,11 @@ func run(cmd string, args ...string) error {
|
||||
|
||||
// token dumps provided token to stdout
|
||||
func generateToken(c *cli.Context) error {
|
||||
if err := raven.SetDSN(sentryDSN); err != nil {
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: sentryDSN,
|
||||
Release: c.App.Version,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appURL, err := url.Parse(ensureURLScheme(c.String("app")))
|
||||
@@ -505,7 +517,7 @@ func isTokenValid(options *carrier.StartOptions, log *zerolog.Logger) (bool, err
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "Could not create access request")
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
// Do not follow redirects
|
||||
client := &http.Client{
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
|
||||
+11
-8
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/getsentry/sentry-go"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/metrics"
|
||||
"github.com/cloudflare/cloudflared/overwatch"
|
||||
"github.com/cloudflare/cloudflared/token"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
"github.com/cloudflare/cloudflared/watcher"
|
||||
)
|
||||
|
||||
@@ -48,7 +50,6 @@ var (
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
metrics.RegisterBuildInfo(BuildType, BuildTime, Version)
|
||||
raven.SetRelease(Version)
|
||||
maxprocs.Set()
|
||||
bInfo := cliutil.GetBuildInfo(BuildType, Version)
|
||||
|
||||
@@ -84,8 +85,10 @@ func main() {
|
||||
app.Commands = commands(cli.ShowVersion)
|
||||
|
||||
tunnel.Init(bInfo, graceShutdownC) // we need this to support the tunnel sub command...
|
||||
access.Init(graceShutdownC)
|
||||
access.Init(graceShutdownC, Version)
|
||||
updater.Init(Version)
|
||||
tracing.Init(Version)
|
||||
token.Init(Version)
|
||||
runApp(app, graceShutdownC)
|
||||
}
|
||||
|
||||
@@ -152,10 +155,10 @@ func action(graceShutdownC chan struct{}) cli.ActionFunc {
|
||||
if isEmptyInvocation(c) {
|
||||
return handleServiceMode(c, graceShutdownC)
|
||||
}
|
||||
tags := make(map[string]string)
|
||||
tags["hostname"] = c.String("hostname")
|
||||
raven.SetTagsContext(tags)
|
||||
raven.CapturePanic(func() { err = tunnel.TunnelCommand(c) }, nil)
|
||||
func() {
|
||||
defer sentry.Recover()
|
||||
err = tunnel.TunnelCommand(c)
|
||||
}()
|
||||
if err != nil {
|
||||
captureError(err)
|
||||
}
|
||||
@@ -183,7 +186,7 @@ func captureError(err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
raven.CaptureError(err, nil)
|
||||
sentry.CaptureException(err)
|
||||
}
|
||||
|
||||
// cloudflared was started without any flags
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package proxydns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -73,7 +74,7 @@ func Run(c *cli.Context) error {
|
||||
log.Fatal().Err(err).Msg("Failed to open the metrics listener")
|
||||
}
|
||||
|
||||
go metrics.ServeMetrics(metricsListener, nil, nil, "", nil, log)
|
||||
go metrics.ServeMetrics(metricsListener, context.Background(), metrics.Config{}, log)
|
||||
|
||||
listener, err := tunneldns.CreateListener(
|
||||
c.String("address"),
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"text/template"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
@@ -52,10 +53,17 @@ func (st *ServiceTemplate) Generate(args *ServiceTemplateArgs) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating %s: %v", st.Path, err)
|
||||
}
|
||||
fileMode := os.FileMode(0644)
|
||||
fileMode := os.FileMode(0o644)
|
||||
if st.FileMode != 0 {
|
||||
fileMode = st.FileMode
|
||||
}
|
||||
|
||||
plistFolder := path.Dir(resolvedPath)
|
||||
err = os.MkdirAll(plistFolder, 0o755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating %s: %v", plistFolder, err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(resolvedPath, buffer.Bytes(), fileMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing %s: %v", resolvedPath, err)
|
||||
|
||||
@@ -14,7 +14,8 @@ import (
|
||||
|
||||
"github.com/coreos/go-systemd/daemon"
|
||||
"github.com/facebookgo/grace/gracenet"
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/google/uuid"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
@@ -24,7 +25,6 @@ import (
|
||||
"github.com/cloudflare/cloudflared/cfapi"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/proxydns"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/ui"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
@@ -82,6 +82,15 @@ const (
|
||||
LogFieldPIDPathname = "pidPathname"
|
||||
LogFieldTmpTraceFilename = "tmpTraceFilename"
|
||||
LogFieldTraceOutputFilepath = "traceOutputFilepath"
|
||||
|
||||
tunnelCmdErrorMessage = `You did not specify any valid additional argument to the cloudflared tunnel command.
|
||||
|
||||
If you are trying to run a Quick Tunnel then you need to explicitly pass the --url flag.
|
||||
Eg. cloudflared tunnel --url localhost:8080/.
|
||||
|
||||
Please note that Quick Tunnels are meant to be ephemeral and should only be used for testing purposes.
|
||||
For production usage, we recommend creating Named Tunnels. (https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/)
|
||||
`
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -91,6 +100,7 @@ var (
|
||||
routeFailMsg = fmt.Sprintf("failed to provision routing, please create it manually via Cloudflare dashboard or UI; "+
|
||||
"most likely you already have a conflicting record there. You can also rerun this command with --%s to overwrite "+
|
||||
"any existing DNS records for this hostname.", overwriteDNSFlag)
|
||||
deprecatedClassicTunnelErr = fmt.Errorf("Classic tunnels have been deprecated, please use Named Tunnels. (https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/)")
|
||||
)
|
||||
|
||||
func Flags() []cli.Flag {
|
||||
@@ -166,21 +176,34 @@ func TunnelCommand(c *cli.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if name := c.String("name"); name != "" { // Start a named tunnel
|
||||
return runAdhocNamedTunnel(sc, name, c.String(CredFileFlag))
|
||||
}
|
||||
|
||||
// Unauthenticated named tunnel on <random>.<quick-tunnels-service>.com
|
||||
shouldRunQuickTunnel := c.IsSet("url") || c.IsSet("hello-world")
|
||||
if !dnsProxyStandAlone(c, nil) && c.String("quick-service") != "" && shouldRunQuickTunnel {
|
||||
return RunQuickTunnel(sc)
|
||||
}
|
||||
|
||||
if ref := config.GetConfiguration().TunnelID; ref != "" {
|
||||
return fmt.Errorf("Use `cloudflared tunnel run` to start tunnel %s", ref)
|
||||
}
|
||||
|
||||
// Unauthenticated named tunnel on <random>.<quick-tunnels-service>.com
|
||||
// For now, default to legacy setup unless quick-service is specified
|
||||
if !dnsProxyStandAlone(c, nil) && c.String("hostname") == "" && c.String("quick-service") != "" {
|
||||
return RunQuickTunnel(sc)
|
||||
// classic tunnel usage is no longer supported
|
||||
if c.String("hostname") != "" {
|
||||
return deprecatedClassicTunnelErr
|
||||
}
|
||||
|
||||
// Start a classic tunnel
|
||||
return runClassicTunnel(sc)
|
||||
if c.IsSet("proxy-dns") {
|
||||
// NamedTunnelProperties are nil since proxy dns server does not need it.
|
||||
// This is supported for legacy reasons: dns proxy server is not a tunnel and ideally should
|
||||
// not run as part of cloudflared tunnel.
|
||||
return StartServer(sc.c, buildInfo, nil, sc.log)
|
||||
}
|
||||
|
||||
return errors.New(tunnelCmdErrorMessage)
|
||||
}
|
||||
|
||||
func Init(info *cliutil.BuildInfo, gracefulShutdown chan struct{}) {
|
||||
@@ -215,11 +238,6 @@ func runAdhocNamedTunnel(sc *subcommandContext, name, credentialsOutputPath stri
|
||||
return nil
|
||||
}
|
||||
|
||||
// runClassicTunnel creates a "classic" non-named tunnel
|
||||
func runClassicTunnel(sc *subcommandContext) error {
|
||||
return StartServer(sc.c, buildInfo, nil, sc.log, sc.isUIEnabled)
|
||||
}
|
||||
|
||||
func routeFromFlag(c *cli.Context) (route cfapi.HostnameRoute, ok bool) {
|
||||
if hostname := c.String("hostname"); hostname != "" {
|
||||
if lbPool := c.String("lb-pool"); lbPool != "" {
|
||||
@@ -235,14 +253,20 @@ func StartServer(
|
||||
info *cliutil.BuildInfo,
|
||||
namedTunnel *connection.NamedTunnelProperties,
|
||||
log *zerolog.Logger,
|
||||
isUIEnabled bool,
|
||||
) error {
|
||||
_ = raven.SetDSN(sentryDSN)
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: sentryDSN,
|
||||
Release: c.App.Version,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
listeners := gracenet.Net{}
|
||||
errC := make(chan error)
|
||||
|
||||
if config.GetConfiguration().Source() == "" {
|
||||
// Only log for locally configured tunnels (Token is blank).
|
||||
if config.GetConfiguration().Source() == "" && c.String(TunnelTokenFlag) == "" {
|
||||
log.Info().Msg(config.ErrNoConfigFile.Error())
|
||||
}
|
||||
|
||||
@@ -322,17 +346,9 @@ func StartServer(
|
||||
return waitToShutdown(&wg, cancel, errC, graceShutdownC, 0, log)
|
||||
}
|
||||
|
||||
url := c.String("url")
|
||||
hostname := c.String("hostname")
|
||||
if url == hostname && url != "" && hostname != "" {
|
||||
errText := "hostname and url shouldn't match. See --help for more information"
|
||||
log.Error().Msg(errText)
|
||||
return fmt.Errorf(errText)
|
||||
}
|
||||
logTransport := logger.CreateTransportLoggerFromContext(c, logger.EnableTerminalLog)
|
||||
|
||||
logTransport := logger.CreateTransportLoggerFromContext(c, isUIEnabled)
|
||||
|
||||
observer := connection.NewObserver(log, logTransport, isUIEnabled)
|
||||
observer := connection.NewObserver(log, logTransport)
|
||||
|
||||
// Send Quick Tunnel URL to UI if applicable
|
||||
var quickTunnelURL string
|
||||
@@ -343,13 +359,21 @@ func StartServer(
|
||||
observer.SendURL(quickTunnelURL)
|
||||
}
|
||||
|
||||
tunnelConfig, dynamicConfig, err := prepareTunnelConfig(c, info, log, logTransport, observer, namedTunnel)
|
||||
tunnelConfig, orchestratorConfig, err := prepareTunnelConfig(c, info, log, logTransport, observer, namedTunnel)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
orchestrator, err := orchestration.NewOrchestrator(ctx, dynamicConfig, tunnelConfig.Tags, tunnelConfig.Log)
|
||||
orchestrator, err := orchestration.NewOrchestrator(ctx, orchestratorConfig, tunnelConfig.Tags, tunnelConfig.Log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -363,12 +387,17 @@ func StartServer(
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
readinessServer := metrics.NewReadyServer(log)
|
||||
readinessServer := metrics.NewReadyServer(log, clientID)
|
||||
observer.RegisterSink(readinessServer)
|
||||
errC <- metrics.ServeMetrics(metricsListener, ctx.Done(), readinessServer, quickTunnelURL, orchestrator, log)
|
||||
metricsConfig := metrics.Config{
|
||||
ReadyServer: readinessServer,
|
||||
QuickTunnelHostname: quickTunnelURL,
|
||||
Orchestrator: orchestrator,
|
||||
}
|
||||
errC <- metrics.ServeMetrics(metricsListener, ctx, metricsConfig, log)
|
||||
}()
|
||||
|
||||
reconnectCh := make(chan supervisor.ReconnectSignal, 1)
|
||||
reconnectCh := make(chan supervisor.ReconnectSignal, c.Int("ha-connections"))
|
||||
if c.IsSet("stdin-control") {
|
||||
log.Info().Msg("Enabling control through stdin")
|
||||
go stdinControl(reconnectCh, log)
|
||||
@@ -383,18 +412,6 @@ func StartServer(
|
||||
errC <- supervisor.StartTunnelDaemon(ctx, tunnelConfig, orchestrator, connectedSignal, reconnectCh, graceShutdownC)
|
||||
}()
|
||||
|
||||
if isUIEnabled {
|
||||
tunnelUI := ui.NewUIModel(
|
||||
info.Version(),
|
||||
hostname,
|
||||
metricsListener.Addr().String(),
|
||||
dynamicConfig.Ingress,
|
||||
tunnelConfig.HAConnections,
|
||||
)
|
||||
app := tunnelUI.Launch(ctx, log, logTransport)
|
||||
observer.RegisterSink(app)
|
||||
}
|
||||
|
||||
gracePeriod, err := gracePeriod(c)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -514,6 +531,13 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||
Usage: "Cloudflare Edge region to connect to. Omit or set to empty to connect to the global region.",
|
||||
EnvVars: []string{"TUNNEL_REGION"},
|
||||
}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{
|
||||
Name: "edge-ip-version",
|
||||
Usage: "Cloudflare Edge ip address version to connect with. {4, 6, auto}",
|
||||
EnvVars: []string{"TUNNEL_EDGE_IP_VERSION"},
|
||||
Value: "4",
|
||||
Hidden: false,
|
||||
}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{
|
||||
Name: tlsconfig.CaCertFlag,
|
||||
Usage: "Certificate Authority authenticating connections with Cloudflare's edge network.",
|
||||
@@ -589,6 +613,12 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||
Value: 5,
|
||||
Hidden: true,
|
||||
}),
|
||||
altsrc.NewIntFlag(&cli.IntFlag{
|
||||
Name: "max-edge-addr-retries",
|
||||
Usage: "Maximum number of times to retry on edge addrs before falling back to a lower protocol",
|
||||
Value: 8,
|
||||
Hidden: true,
|
||||
}),
|
||||
// Note TUN-3758 , we use Int because UInt is not supported with altsrc
|
||||
altsrc.NewIntFlag(&cli.IntFlag{
|
||||
Name: "retries",
|
||||
@@ -647,9 +677,9 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||
}),
|
||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||
Name: uiFlag,
|
||||
Usage: "Launch tunnel UI. Tunnel logs are scrollable via 'j', 'k', or arrow keys.",
|
||||
Usage: "(depreciated) Launch tunnel UI. Tunnel logs are scrollable via 'j', 'k', or arrow keys.",
|
||||
Value: false,
|
||||
Hidden: shouldHide,
|
||||
Hidden: true,
|
||||
}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{
|
||||
Name: "quick-service",
|
||||
@@ -663,6 +693,13 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||
EnvVars: []string{"TUNNEL_MAX_FETCH_SIZE"},
|
||||
Hidden: true,
|
||||
}),
|
||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||
Name: "post-quantum",
|
||||
Usage: "When given creates an experimental post-quantum secure tunnel",
|
||||
Aliases: []string{"pq"},
|
||||
EnvVars: []string{"TUNNEL_POST_QUANTUM"},
|
||||
Hidden: FipsEnabled,
|
||||
}),
|
||||
selectProtocolFlag,
|
||||
overwriteDNSFlag,
|
||||
}...)
|
||||
@@ -821,6 +858,13 @@ func configureProxyFlags(shouldHide bool) []cli.Flag {
|
||||
EnvVars: []string{"TUNNEL_NO_CHUNKED_ENCODING"},
|
||||
Hidden: shouldHide,
|
||||
}),
|
||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||
Name: ingress.Http2OriginFlag,
|
||||
Usage: "Enables HTTP/2 origin servers.",
|
||||
EnvVars: []string{"TUNNEL_ORIGIN_ENABLE_HTTP2"},
|
||||
Hidden: shouldHide,
|
||||
Value: false,
|
||||
}),
|
||||
}
|
||||
return append(flags, sshFlags(shouldHide)...)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
mathRand "math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -18,6 +21,7 @@ import (
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
@@ -28,7 +32,6 @@ import (
|
||||
"github.com/cloudflare/cloudflared/supervisor"
|
||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/validation"
|
||||
)
|
||||
|
||||
const LogFieldOriginCertPath = "originCertPath"
|
||||
@@ -39,9 +42,10 @@ var (
|
||||
serviceUrl = developerPortal + "/reference/service/"
|
||||
argumentsUrl = developerPortal + "/reference/arguments/"
|
||||
|
||||
LogFieldHostname = "hostname"
|
||||
secretFlags = [2]*altsrc.StringFlag{credentialsContentsFlag, tunnelTokenFlag}
|
||||
defaultFeatures = []string{supervisor.FeatureAllowRemoteConfig, supervisor.FeatureSerializedHeaders, supervisor.FeatureDatagramV2, supervisor.FeatureQUICSupportEOF}
|
||||
|
||||
secretFlags = [2]*altsrc.StringFlag{credentialsContentsFlag, tunnelTokenFlag}
|
||||
configFlags = []string{"autoupdate-freq", "no-autoupdate", "retries", "protocol", "loglevel", "transport-loglevel", "origincert", "metrics", "metrics-update-freq", "edge-ip-version"}
|
||||
)
|
||||
|
||||
// returns the first path that contains a cert.pem file. If none of the DefaultConfigSearchDirectories
|
||||
@@ -120,7 +124,7 @@ func isSecretEnvVar(key string) bool {
|
||||
}
|
||||
|
||||
func dnsProxyStandAlone(c *cli.Context, namedTunnel *connection.NamedTunnelProperties) bool {
|
||||
return c.IsSet("proxy-dns") && (!c.IsSet("hostname") && !c.IsSet("tag") && !c.IsSet("hello-world") && namedTunnel == nil)
|
||||
return c.IsSet("proxy-dns") && (!c.IsSet("tag") && !c.IsSet("hello-world") && namedTunnel == nil)
|
||||
}
|
||||
|
||||
func findOriginCert(originCertPath string, log *zerolog.Logger) (string, error) {
|
||||
@@ -186,109 +190,79 @@ func prepareTunnelConfig(
|
||||
observer *connection.Observer,
|
||||
namedTunnel *connection.NamedTunnelProperties,
|
||||
) (*supervisor.TunnelConfig, *orchestration.Config, error) {
|
||||
isNamedTunnel := namedTunnel != nil
|
||||
|
||||
configHostname := c.String("hostname")
|
||||
hostname, err := validation.ValidateHostname(configHostname)
|
||||
clientID, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
log.Err(err).Str(LogFieldHostname, configHostname).Msg("Invalid hostname")
|
||||
return nil, nil, errors.Wrap(err, "Invalid hostname")
|
||||
return nil, nil, errors.Wrap(err, "can't generate connector UUID")
|
||||
}
|
||||
clientID := c.String("id")
|
||||
if !c.IsSet("id") {
|
||||
clientID, err = generateRandomClientID(log)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Info().Msgf("Generated Connector ID: %s", clientID)
|
||||
tags, err := NewTagSliceFromCLI(c.StringSlice("tag"))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Tag parse failure")
|
||||
return nil, nil, errors.Wrap(err, "Tag parse failure")
|
||||
}
|
||||
|
||||
tags = append(tags, tunnelpogs.Tag{Name: "ID", Value: clientID})
|
||||
|
||||
var (
|
||||
ingressRules ingress.Ingress
|
||||
classicTunnel *connection.ClassicTunnelProperties
|
||||
)
|
||||
tags = append(tags, tunnelpogs.Tag{Name: "ID", Value: clientID.String()})
|
||||
|
||||
transportProtocol := c.String("protocol")
|
||||
needPQ := c.Bool("post-quantum")
|
||||
if needPQ {
|
||||
if FipsEnabled {
|
||||
return nil, nil, fmt.Errorf("post-quantum not supported in FIPS mode")
|
||||
}
|
||||
// Error if the user tries to force a non-quic transport protocol
|
||||
if transportProtocol != connection.AutoSelectFlag && transportProtocol != connection.QUIC.String() {
|
||||
return nil, nil, fmt.Errorf("post-quantum is only supported with the quic transport")
|
||||
}
|
||||
transportProtocol = connection.QUIC.String()
|
||||
}
|
||||
|
||||
protocolFetcher := edgediscovery.ProtocolPercentage
|
||||
|
||||
cfg := config.GetConfiguration()
|
||||
if isNamedTunnel {
|
||||
clientUUID, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "can't generate connector UUID")
|
||||
}
|
||||
log.Info().Msgf("Generated Connector ID: %s", clientUUID)
|
||||
features := append(c.StringSlice("features"), supervisor.FeatureSerializedHeaders)
|
||||
if c.IsSet(TunnelTokenFlag) {
|
||||
if transportProtocol == connection.AutoSelectFlag {
|
||||
protocolFetcher = func() (edgediscovery.ProtocolPercents, error) {
|
||||
// If the Tunnel is remotely managed and no protocol is set, we prefer QUIC, but still allow fall-back.
|
||||
preferQuic := []edgediscovery.ProtocolPercent{
|
||||
{
|
||||
Protocol: connection.QUIC.String(),
|
||||
Percentage: 100,
|
||||
},
|
||||
{
|
||||
Protocol: connection.HTTP2.String(),
|
||||
Percentage: 100,
|
||||
},
|
||||
}
|
||||
return preferQuic, nil
|
||||
features := append(c.StringSlice("features"), defaultFeatures...)
|
||||
if needPQ {
|
||||
features = append(features, supervisor.FeaturePostQuantum)
|
||||
}
|
||||
if c.IsSet(TunnelTokenFlag) {
|
||||
if transportProtocol == connection.AutoSelectFlag {
|
||||
protocolFetcher = func() (edgediscovery.ProtocolPercents, error) {
|
||||
// If the Tunnel is remotely managed and no protocol is set, we prefer QUIC, but still allow fall-back.
|
||||
preferQuic := []edgediscovery.ProtocolPercent{
|
||||
{
|
||||
Protocol: connection.QUIC.String(),
|
||||
Percentage: 100,
|
||||
},
|
||||
{
|
||||
Protocol: connection.HTTP2.String(),
|
||||
Percentage: 100,
|
||||
},
|
||||
}
|
||||
return preferQuic, nil
|
||||
}
|
||||
features = append(features, supervisor.FeatureAllowRemoteConfig)
|
||||
log.Info().Msg("Will be fetching remotely managed configuration from Cloudflare API. Defaulting to protocol: quic")
|
||||
}
|
||||
namedTunnel.Client = tunnelpogs.ClientInfo{
|
||||
ClientID: clientUUID[:],
|
||||
Features: dedup(features),
|
||||
Version: info.Version(),
|
||||
Arch: info.OSArch(),
|
||||
}
|
||||
ingressRules, err = ingress.ParseIngress(cfg)
|
||||
if err != nil && err != ingress.ErrNoIngressRules {
|
||||
log.Info().Msg("Will be fetching remotely managed configuration from Cloudflare API. Defaulting to protocol: quic")
|
||||
}
|
||||
namedTunnel.Client = tunnelpogs.ClientInfo{
|
||||
ClientID: clientID[:],
|
||||
Features: dedup(features),
|
||||
Version: info.Version(),
|
||||
Arch: info.OSArch(),
|
||||
}
|
||||
cfg := config.GetConfiguration()
|
||||
ingressRules, err := ingress.ParseIngress(cfg)
|
||||
if err != nil && err != ingress.ErrNoIngressRules {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Only for quick tunnels will we attempt to parse the --url flag for a tunnel ingress rule
|
||||
if ingressRules.IsEmpty() && c.IsSet("url") && namedTunnel.QuickTunnelUrl != "" {
|
||||
ingressRules, err = ingress.NewSingleOrigin(c, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !ingressRules.IsEmpty() && c.IsSet("url") {
|
||||
return nil, nil, ingress.ErrURLIncompatibleWithIngress
|
||||
}
|
||||
} else {
|
||||
|
||||
originCertPath := c.String("origincert")
|
||||
originCertLog := log.With().
|
||||
Str(LogFieldOriginCertPath, originCertPath).
|
||||
Logger()
|
||||
|
||||
originCert, err := getOriginCert(originCertPath, &originCertLog)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "Error getting origin cert")
|
||||
}
|
||||
|
||||
classicTunnel = &connection.ClassicTunnelProperties{
|
||||
Hostname: hostname,
|
||||
OriginCert: originCert,
|
||||
// turn off use of reconnect token and auth refresh when using named tunnels
|
||||
UseReconnectToken: !isNamedTunnel && c.Bool("use-reconnect-token"),
|
||||
}
|
||||
}
|
||||
|
||||
// Convert single-origin configuration into multi-origin configuration.
|
||||
if ingressRules.IsEmpty() {
|
||||
ingressRules, err = ingress.NewSingleOrigin(c, !isNamedTunnel)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, nil, ingress.ErrNoIngressRules
|
||||
}
|
||||
|
||||
warpRoutingEnabled := isWarpRoutingEnabled(cfg.WarpRouting, isNamedTunnel)
|
||||
protocolSelector, err := connection.NewProtocolSelector(transportProtocol, warpRoutingEnabled, namedTunnel, protocolFetcher, supervisor.ResolveTTL, log)
|
||||
protocolSelector, err := connection.NewProtocolSelector(transportProtocol, cfg.WarpRouting.Enabled, namedTunnel, protocolFetcher, supervisor.ResolveTTL, log, c.Bool("post-quantum"))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -322,14 +296,28 @@ func prepareTunnelConfig(
|
||||
CompressionSetting: h2mux.CompressionSetting(uint64(c.Int("compression-quality"))),
|
||||
MetricsUpdateFreq: c.Duration("metrics-update-freq"),
|
||||
}
|
||||
edgeIPVersion, err := parseConfigIPVersion(c.String("edge-ip-version"))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var pqKexIdx int
|
||||
if needPQ {
|
||||
pqKexIdx = mathRand.Intn(len(supervisor.PQKexes))
|
||||
log.Info().Msgf(
|
||||
"Using experimental hybrid post-quantum key agreement %s",
|
||||
supervisor.PQKexNames[supervisor.PQKexes[pqKexIdx]],
|
||||
)
|
||||
}
|
||||
|
||||
tunnelConfig := &supervisor.TunnelConfig{
|
||||
GracePeriod: gracePeriod,
|
||||
ReplaceExisting: c.Bool("force"),
|
||||
OSArch: info.OSArch(),
|
||||
ClientID: clientID,
|
||||
ClientID: clientID.String(),
|
||||
EdgeAddrs: c.StringSlice("edge"),
|
||||
Region: c.String("region"),
|
||||
EdgeIPVersion: edgeIPVersion,
|
||||
HAConnections: c.Int("ha-connections"),
|
||||
IncidentLookup: supervisor.NewIncidentLookup(),
|
||||
IsAutoupdated: c.Bool("is-autoupdated"),
|
||||
@@ -340,19 +328,40 @@ func prepareTunnelConfig(
|
||||
Observer: observer,
|
||||
ReportedVersion: info.Version(),
|
||||
// Note TUN-3758 , we use Int because UInt is not supported with altsrc
|
||||
Retries: uint(c.Int("retries")),
|
||||
RunFromTerminal: isRunningFromTerminal(),
|
||||
NamedTunnel: namedTunnel,
|
||||
ClassicTunnel: classicTunnel,
|
||||
MuxerConfig: muxerConfig,
|
||||
ProtocolSelector: protocolSelector,
|
||||
EdgeTLSConfigs: edgeTLSConfigs,
|
||||
Retries: uint(c.Int("retries")),
|
||||
RunFromTerminal: isRunningFromTerminal(),
|
||||
NamedTunnel: namedTunnel,
|
||||
MuxerConfig: muxerConfig,
|
||||
ProtocolSelector: protocolSelector,
|
||||
EdgeTLSConfigs: edgeTLSConfigs,
|
||||
NeedPQ: needPQ,
|
||||
PQKexIdx: pqKexIdx,
|
||||
MaxEdgeAddrRetries: uint8(c.Int("max-edge-addr-retries")),
|
||||
}
|
||||
dynamicConfig := &orchestration.Config{
|
||||
packetConfig, err := newPacketConfig(c, log)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("ICMP proxy feature is disabled")
|
||||
} else {
|
||||
tunnelConfig.PacketConfig = packetConfig
|
||||
}
|
||||
orchestratorConfig := &orchestration.Config{
|
||||
Ingress: &ingressRules,
|
||||
WarpRoutingEnabled: warpRoutingEnabled,
|
||||
WarpRouting: ingress.NewWarpRoutingConfig(&cfg.WarpRouting),
|
||||
ConfigurationFlags: parseConfigFlags(c),
|
||||
}
|
||||
return tunnelConfig, dynamicConfig, nil
|
||||
return tunnelConfig, orchestratorConfig, nil
|
||||
}
|
||||
|
||||
func parseConfigFlags(c *cli.Context) map[string]string {
|
||||
result := make(map[string]string)
|
||||
|
||||
for _, flag := range configFlags {
|
||||
if v := c.String(flag); c.IsSet(flag) && v != "" {
|
||||
result[flag] = v
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func gracePeriod(c *cli.Context) (time.Duration, error) {
|
||||
@@ -363,10 +372,6 @@ func gracePeriod(c *cli.Context) (time.Duration, error) {
|
||||
return period, nil
|
||||
}
|
||||
|
||||
func isWarpRoutingEnabled(warpConfig config.WarpRoutingConfig, isNamedTunnel bool) bool {
|
||||
return warpConfig.Enabled && isNamedTunnel
|
||||
}
|
||||
|
||||
func isRunningFromTerminal() bool {
|
||||
return terminal.IsTerminal(int(os.Stdout.Fd()))
|
||||
}
|
||||
@@ -389,3 +394,162 @@ func dedup(slice []string) []string {
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// ParseConfigIPVersion returns the IP version from possible expected values from config
|
||||
func parseConfigIPVersion(version string) (v allregions.ConfigIPVersion, err error) {
|
||||
switch version {
|
||||
case "4":
|
||||
v = allregions.IPv4Only
|
||||
case "6":
|
||||
v = allregions.IPv6Only
|
||||
case "auto":
|
||||
v = allregions.Auto
|
||||
default: // unspecified or invalid
|
||||
err = fmt.Errorf("invalid value for edge-ip-version: %s", version)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func newPacketConfig(c *cli.Context, logger *zerolog.Logger) (*ingress.GlobalRouterConfig, error) {
|
||||
ipv4Src, err := determineICMPv4Src(c.String("icmpv4-src"), logger)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to determine IPv4 source address for ICMP proxy")
|
||||
}
|
||||
logger.Info().Msgf("ICMP proxy will use %s as source for IPv4", ipv4Src)
|
||||
|
||||
ipv6Src, zone, err := determineICMPv6Src(c.String("icmpv6-src"), logger, ipv4Src)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to determine IPv6 source address for ICMP proxy")
|
||||
}
|
||||
if zone != "" {
|
||||
logger.Info().Msgf("ICMP proxy will use %s in zone %s as source for IPv6", ipv6Src, zone)
|
||||
} else {
|
||||
logger.Info().Msgf("ICMP proxy will use %s as source for IPv6", ipv6Src)
|
||||
}
|
||||
|
||||
icmpRouter, err := ingress.NewICMPRouter(ipv4Src, ipv6Src, zone, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ingress.GlobalRouterConfig{
|
||||
ICMPRouter: icmpRouter,
|
||||
IPv4Src: ipv4Src,
|
||||
IPv6Src: ipv6Src,
|
||||
Zone: zone,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func determineICMPv4Src(userDefinedSrc string, logger *zerolog.Logger) (netip.Addr, error) {
|
||||
if userDefinedSrc != "" {
|
||||
addr, err := netip.ParseAddr(userDefinedSrc)
|
||||
if err != nil {
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
if addr.Is4() {
|
||||
return addr, nil
|
||||
}
|
||||
return netip.Addr{}, fmt.Errorf("expect IPv4, but %s is IPv6", userDefinedSrc)
|
||||
}
|
||||
|
||||
addr, err := findLocalAddr(net.ParseIP("192.168.0.1"), 53)
|
||||
if err != nil {
|
||||
addr = netip.IPv4Unspecified()
|
||||
logger.Debug().Err(err).Msgf("Failed to determine the IPv4 for this machine. It will use %s to send/listen for ICMPv4 echo", addr)
|
||||
}
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
type interfaceIP struct {
|
||||
name string
|
||||
ip net.IP
|
||||
}
|
||||
|
||||
func determineICMPv6Src(userDefinedSrc string, logger *zerolog.Logger, ipv4Src netip.Addr) (addr netip.Addr, zone string, err error) {
|
||||
if userDefinedSrc != "" {
|
||||
userDefinedIP, zone, _ := strings.Cut(userDefinedSrc, "%")
|
||||
addr, err := netip.ParseAddr(userDefinedIP)
|
||||
if err != nil {
|
||||
return netip.Addr{}, "", err
|
||||
}
|
||||
if addr.Is6() {
|
||||
return addr, zone, nil
|
||||
}
|
||||
return netip.Addr{}, "", fmt.Errorf("expect IPv6, but %s is IPv4", userDefinedSrc)
|
||||
}
|
||||
|
||||
// Loop through all the interfaces, the preference is
|
||||
// 1. The interface where ipv4Src is in
|
||||
// 2. Interface with IPv6 address
|
||||
// 3. Unspecified interface
|
||||
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return netip.IPv6Unspecified(), "", nil
|
||||
}
|
||||
|
||||
interfacesWithIPv6 := make([]interfaceIP, 0)
|
||||
for _, interf := range interfaces {
|
||||
interfaceAddrs, err := interf.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
foundIPv4SrcInterface := false
|
||||
for _, interfaceAddr := range interfaceAddrs {
|
||||
if ipnet, ok := interfaceAddr.(*net.IPNet); ok {
|
||||
ip := ipnet.IP
|
||||
if ip.Equal(ipv4Src.AsSlice()) {
|
||||
foundIPv4SrcInterface = true
|
||||
}
|
||||
if ip.To4() == nil {
|
||||
interfacesWithIPv6 = append(interfacesWithIPv6, interfaceIP{
|
||||
name: interf.Name,
|
||||
ip: ip,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// Found the interface of ipv4Src. Loop through the addresses to see if there is an IPv6
|
||||
if foundIPv4SrcInterface {
|
||||
for _, interfaceAddr := range interfaceAddrs {
|
||||
if ipnet, ok := interfaceAddr.(*net.IPNet); ok {
|
||||
ip := ipnet.IP
|
||||
if ip.To4() == nil {
|
||||
addr, err := netip.ParseAddr(ip.String())
|
||||
if err == nil {
|
||||
return addr, interf.Name, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, interf := range interfacesWithIPv6 {
|
||||
addr, err := netip.ParseAddr(interf.ip.String())
|
||||
if err == nil {
|
||||
return addr, interf.name, nil
|
||||
}
|
||||
}
|
||||
logger.Debug().Err(err).Msgf("Failed to determine the IPv6 for this machine. It will use %s to send/listen for ICMPv6 echo", netip.IPv6Unspecified())
|
||||
|
||||
return netip.IPv6Unspecified(), "", nil
|
||||
}
|
||||
|
||||
// FindLocalAddr tries to dial UDP and returns the local address picked by the OS
|
||||
func findLocalAddr(dst net.IP, port int) (netip.Addr, error) {
|
||||
udpConn, err := net.DialUDP("udp", nil, &net.UDPAddr{
|
||||
IP: dst,
|
||||
Port: port,
|
||||
})
|
||||
if err != nil {
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer udpConn.Close()
|
||||
localAddrPort, err := netip.ParseAddrPort(udpConn.LocalAddr().String())
|
||||
if err != nil {
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
localAddr := localAddrPort.Addr()
|
||||
return localAddr, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package tunnel
|
||||
|
||||
var FipsEnabled bool
|
||||
@@ -78,7 +78,6 @@ func RunQuickTunnel(sc *subcommandContext) error {
|
||||
buildInfo,
|
||||
&connection.NamedTunnelProperties{Credentials: credentials, QuickTunnelUrl: data.Result.Hostname},
|
||||
sc.log,
|
||||
sc.isUIEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,9 @@ func (e errInvalidJSONCredential) Error() string {
|
||||
// subcommandContext carries structs shared between subcommands, to reduce number of arguments needed to
|
||||
// pass between subcommands, and make sure they are only initialized once
|
||||
type subcommandContext struct {
|
||||
c *cli.Context
|
||||
log *zerolog.Logger
|
||||
isUIEnabled bool
|
||||
fs fileSystem
|
||||
c *cli.Context
|
||||
log *zerolog.Logger
|
||||
fs fileSystem
|
||||
|
||||
// These fields should be accessed using their respective Getter
|
||||
tunnelstoreClient cfapi.Client
|
||||
@@ -42,16 +41,10 @@ type subcommandContext struct {
|
||||
}
|
||||
|
||||
func newSubcommandContext(c *cli.Context) (*subcommandContext, error) {
|
||||
isUIEnabled := c.IsSet(uiFlag) && c.String("name") != ""
|
||||
|
||||
// If UI is enabled, terminal log output should be disabled -- log should be written into a UI log window instead
|
||||
log := logger.CreateLoggerFromContext(c, isUIEnabled)
|
||||
|
||||
return &subcommandContext{
|
||||
c: c,
|
||||
log: log,
|
||||
isUIEnabled: isUIEnabled,
|
||||
fs: realFileSystem{},
|
||||
c: c,
|
||||
log: logger.CreateLoggerFromContext(c, logger.EnableTerminalLog),
|
||||
fs: realFileSystem{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -81,7 +74,7 @@ func (sc *subcommandContext) client() (cfapi.Client, error) {
|
||||
sc.c.String("api-url"),
|
||||
credential.cert.AccountID,
|
||||
credential.cert.ZoneID,
|
||||
credential.cert.ServiceKey,
|
||||
credential.cert.APIToken,
|
||||
userAgent,
|
||||
sc.log,
|
||||
)
|
||||
@@ -312,7 +305,6 @@ func (sc *subcommandContext) runWithCredentials(credentials connection.Credentia
|
||||
buildInfo,
|
||||
&connection.NamedTunnelProperties{Credentials: credentials},
|
||||
sc.log,
|
||||
sc.isUIEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ func Test_subcommandContext_findCredentials(t *testing.T) {
|
||||
type fields struct {
|
||||
c *cli.Context
|
||||
log *zerolog.Logger
|
||||
isUIEnabled bool
|
||||
fs fileSystem
|
||||
tunnelstoreClient cfapi.Client
|
||||
userCredential *userCredential
|
||||
@@ -168,7 +167,6 @@ func Test_subcommandContext_findCredentials(t *testing.T) {
|
||||
sc := &subcommandContext{
|
||||
c: tt.fields.c,
|
||||
log: tt.fields.log,
|
||||
isUIEnabled: tt.fields.isUIEnabled,
|
||||
fs: tt.fields.fs,
|
||||
tunnelstoreClient: tt.fields.tunnelstoreClient,
|
||||
userCredential: tt.fields.userCredential,
|
||||
@@ -307,7 +305,6 @@ func Test_subcommandContext_Delete(t *testing.T) {
|
||||
sc := &subcommandContext{
|
||||
c: tt.fields.c,
|
||||
log: tt.fields.log,
|
||||
isUIEnabled: tt.fields.isUIEnabled,
|
||||
fs: tt.fields.fs,
|
||||
tunnelstoreClient: tt.fields.tunnelstoreClient,
|
||||
userCredential: tt.fields.userCredential,
|
||||
|
||||
@@ -23,12 +23,12 @@ func (sc *subcommandContext) listVirtualNetworks(filter *cfapi.VnetFilter) ([]*c
|
||||
return client.ListVirtualNetworks(filter)
|
||||
}
|
||||
|
||||
func (sc *subcommandContext) deleteVirtualNetwork(vnetId uuid.UUID) error {
|
||||
func (sc *subcommandContext) deleteVirtualNetwork(vnetId uuid.UUID, force bool) error {
|
||||
client, err := sc.client()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, noClientMsg)
|
||||
}
|
||||
return client.DeleteVirtualNetwork(vnetId)
|
||||
return client.DeleteVirtualNetwork(vnetId, force)
|
||||
}
|
||||
|
||||
func (sc *subcommandContext) updateVirtualNetwork(vnetId uuid.UUID, updates cfapi.UpdateVirtualNetwork) error {
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
"golang.org/x/net/idna"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cfapi"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
@@ -140,6 +140,13 @@ var (
|
||||
EnvVars: []string{"TUNNEL_TRANSPORT_PROTOCOL"},
|
||||
Hidden: true,
|
||||
})
|
||||
postQuantumFlag = altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||
Name: "post-quantum",
|
||||
Usage: "When given creates an experimental post-quantum secure tunnel",
|
||||
Aliases: []string{"pq"},
|
||||
EnvVars: []string{"TUNNEL_POST_QUANTUM"},
|
||||
Hidden: FipsEnabled,
|
||||
})
|
||||
sortInfoByFlag = &cli.StringFlag{
|
||||
Name: "sort-by",
|
||||
Value: "createdAt",
|
||||
@@ -169,6 +176,16 @@ var (
|
||||
Usage: "Base64 encoded secret to set for the tunnel. The decoded secret must be at least 32 bytes long. If not specified, a random 32-byte secret will be generated.",
|
||||
EnvVars: []string{"TUNNEL_CREATE_SECRET"},
|
||||
}
|
||||
icmpv4SrcFlag = &cli.StringFlag{
|
||||
Name: "icmpv4-src",
|
||||
Usage: "Source address to send/receive ICMPv4 messages. If not provided cloudflared will dial a local address to determine the source IP or fallback to 0.0.0.0.",
|
||||
EnvVars: []string{"TUNNEL_ICMPV4_SRC"},
|
||||
}
|
||||
icmpv6SrcFlag = &cli.StringFlag{
|
||||
Name: "icmpv6-src",
|
||||
Usage: "Source address and the interface name to send/receive ICMPv6 messages. If not provided cloudflared will dial a local address to determine the source IP or fallback to ::.",
|
||||
EnvVars: []string{"TUNNEL_ICMPV6_SRC"},
|
||||
}
|
||||
)
|
||||
|
||||
func buildCreateCommand() *cli.Command {
|
||||
@@ -602,9 +619,12 @@ func buildRunCommand() *cli.Command {
|
||||
forceFlag,
|
||||
credentialsFileFlag,
|
||||
credentialsContentsFlag,
|
||||
postQuantumFlag,
|
||||
selectProtocolFlag,
|
||||
featuresFlag,
|
||||
tunnelTokenFlag,
|
||||
icmpv4SrcFlag,
|
||||
icmpv6SrcFlag,
|
||||
}
|
||||
flags = append(flags, configureProxyFlags(false)...)
|
||||
return &cli.Command{
|
||||
@@ -763,7 +783,7 @@ func tokenCommand(c *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s", encodedToken)
|
||||
fmt.Println(encodedToken)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -801,7 +821,7 @@ Further information about managing Cloudflare WARP traffic to your tunnel is ava
|
||||
Name: "lb",
|
||||
Action: cliutil.ConfiguredAction(routeLbCommand),
|
||||
Usage: "Use this tunnel as a load balancer origin, creating pool and load balancer if necessary",
|
||||
UsageText: "cloudflared tunnel route dns [TUNNEL] [HOSTNAME] [LB-POOL]",
|
||||
UsageText: "cloudflared tunnel route lb [TUNNEL] [HOSTNAME] [LB-POOL-NAME]",
|
||||
Description: `Creates Load Balancer with an origin pool that points to the tunnel.`,
|
||||
},
|
||||
buildRouteIPSubcommand(),
|
||||
|
||||
@@ -33,6 +33,12 @@ var (
|
||||
Aliases: []string{"c"},
|
||||
Usage: "A new comment describing the purpose of the virtual network.",
|
||||
}
|
||||
vnetForceDeleteFlag = &cli.BoolFlag{
|
||||
Name: "force",
|
||||
Aliases: []string{"f"},
|
||||
Usage: "Force the deletion of the virtual network even if it is being relied upon by other resources. Those" +
|
||||
"resources will either be deleted (e.g. IP Routes) or moved to the current default virutal network.",
|
||||
}
|
||||
)
|
||||
|
||||
func buildVirtualNetworkSubcommand(hidden bool) *cli.Command {
|
||||
@@ -82,6 +88,7 @@ be the current default.`,
|
||||
UsageText: "cloudflared tunnel [--config FILEPATH] network delete VIRTUAL_NETWORK",
|
||||
Description: `Deletes the virtual network (given its ID or name). This is only possible if that virtual network is unused.
|
||||
A virtual network may be used by IP routes or by WARP devices.`,
|
||||
Flags: []cli.Flag{vnetForceDeleteFlag},
|
||||
Hidden: hidden,
|
||||
},
|
||||
{
|
||||
@@ -188,7 +195,7 @@ func deleteVirtualNetworkCommand(c *cli.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.NArg() != 1 {
|
||||
if c.NArg() < 1 {
|
||||
return errors.New("You must supply exactly one argument, either the ID or name of the virtual network to delete")
|
||||
}
|
||||
|
||||
@@ -198,7 +205,12 @@ func deleteVirtualNetworkCommand(c *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sc.deleteVirtualNetwork(vnetId); err != nil {
|
||||
forceDelete := false
|
||||
if c.IsSet(vnetForceDeleteFlag.Name) {
|
||||
forceDelete = c.Bool(vnetForceDeleteFlag.Name)
|
||||
}
|
||||
|
||||
if err := sc.deleteVirtualNetwork(vnetId, forceDelete); err != nil {
|
||||
return errors.Wrap(err, "API error")
|
||||
}
|
||||
fmt.Printf("Successfully deleted virtual network '%s'\n", input)
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
)
|
||||
|
||||
type connState struct {
|
||||
location string
|
||||
}
|
||||
|
||||
type uiModel struct {
|
||||
version string
|
||||
edgeURL string
|
||||
metricsURL string
|
||||
localServices []string
|
||||
connections []connState
|
||||
}
|
||||
|
||||
type palette struct {
|
||||
url string
|
||||
connected string
|
||||
defaultText string
|
||||
disconnected string
|
||||
reconnecting string
|
||||
unregistered string
|
||||
}
|
||||
|
||||
func NewUIModel(version, hostname, metricsURL string, ing *ingress.Ingress, haConnections int) *uiModel {
|
||||
localServices := make([]string, len(ing.Rules))
|
||||
for i, rule := range ing.Rules {
|
||||
localServices[i] = rule.Service.String()
|
||||
}
|
||||
return &uiModel{
|
||||
version: version,
|
||||
edgeURL: hostname,
|
||||
metricsURL: metricsURL,
|
||||
localServices: localServices,
|
||||
connections: make([]connState, haConnections),
|
||||
}
|
||||
}
|
||||
|
||||
func (data *uiModel) Launch(
|
||||
ctx context.Context,
|
||||
log, transportLog *zerolog.Logger,
|
||||
) connection.EventSink {
|
||||
// Configure the logger to stream logs into the textview
|
||||
|
||||
// Add TextView as a group to write output to
|
||||
logTextView := NewDynamicColorTextView()
|
||||
// TODO: Format log for UI
|
||||
//log.Add(logTextView, logger.NewUIFormatter(time.RFC3339), logLevels...)
|
||||
//transportLog.Add(logTextView, logger.NewUIFormatter(time.RFC3339), logLevels...)
|
||||
|
||||
// Construct the UI
|
||||
palette := palette{
|
||||
url: "lightblue",
|
||||
connected: "lime",
|
||||
defaultText: "white",
|
||||
disconnected: "red",
|
||||
reconnecting: "orange",
|
||||
unregistered: "orange",
|
||||
}
|
||||
|
||||
app := tview.NewApplication()
|
||||
|
||||
grid := tview.NewGrid().SetGap(1, 0)
|
||||
frame := tview.NewFrame(grid)
|
||||
header := fmt.Sprintf("cloudflared [::b]%s", data.version)
|
||||
|
||||
frame.AddText(header, true, tview.AlignLeft, tcell.ColorWhite)
|
||||
|
||||
// Create table to store connection info and status
|
||||
connTable := tview.NewTable()
|
||||
// SetColumns takes a value for each column, representing the size of the column
|
||||
// Numbers <= 0 represent proportional widths and positive numbers represent absolute widths
|
||||
grid.SetColumns(20, 0)
|
||||
|
||||
// SetRows takes a value for each row, representing the size of the row
|
||||
grid.SetRows(1, 1, len(data.connections), 1, 0)
|
||||
|
||||
// AddItem takes a primitive tview type, row, column, rowSpan, columnSpan, minGridHeight, minGridWidth, and focus
|
||||
grid.AddItem(tview.NewTextView().SetText("Tunnel:"), 0, 0, 1, 1, 0, 0, false)
|
||||
grid.AddItem(tview.NewTextView().SetText("Status:"), 1, 0, 1, 1, 0, 0, false)
|
||||
grid.AddItem(tview.NewTextView().SetText("Connections:"), 2, 0, 1, 1, 0, 0, false)
|
||||
|
||||
grid.AddItem(tview.NewTextView().SetText("Metrics:"), 3, 0, 1, 1, 0, 0, false)
|
||||
|
||||
tunnelHostText := tview.NewTextView().SetText(data.edgeURL)
|
||||
|
||||
grid.AddItem(tunnelHostText, 0, 1, 1, 1, 0, 0, false)
|
||||
status := fmt.Sprintf("[%s]\u2022[%s] Proxying to [%s::b]%s", palette.connected, palette.defaultText, palette.url, strings.Join(data.localServices, ", "))
|
||||
grid.AddItem(NewDynamicColorTextView().SetText(status), 1, 1, 1, 1, 0, 0, false)
|
||||
|
||||
grid.AddItem(connTable, 2, 1, 1, 1, 0, 0, false)
|
||||
|
||||
grid.AddItem(NewDynamicColorTextView().SetText(fmt.Sprintf("Metrics at [%s::b]http://%s/metrics", palette.url, data.metricsURL)), 3, 1, 1, 1, 0, 0, false)
|
||||
|
||||
// Add TextView to stream logs
|
||||
// Logs are displayed in a new grid so a border can be set around them
|
||||
logGrid := tview.NewGrid().SetBorders(true).AddItem(logTextView.SetChangedFunc(handleNewText(app, logTextView)), 0, 0, 5, 2, 0, 0, false)
|
||||
// LogFrame holds the Logs header as well as the grid with the textView for streamed logs
|
||||
logFrame := tview.NewFrame(logGrid).AddText("[::b]Logs:[::-]", true, tview.AlignLeft, tcell.ColorWhite).SetBorders(0, 0, 0, 0, 0, 0)
|
||||
// Footer for log frame
|
||||
logFrame.AddText("[::d]Use Ctrl+C to exit[::-]", false, tview.AlignRight, tcell.ColorWhite)
|
||||
grid.AddItem(logFrame, 4, 0, 5, 2, 0, 0, false)
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
app.Stop()
|
||||
return
|
||||
}()
|
||||
|
||||
go func() {
|
||||
if err := app.SetRoot(frame, true).Run(); err != nil {
|
||||
log.Error().Msgf("Error launching UI: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return connection.EventSinkFunc(func(event connection.Event) {
|
||||
switch event.EventType {
|
||||
case connection.Connected:
|
||||
data.setConnTableCell(event, connTable, palette)
|
||||
case connection.Disconnected, connection.Reconnecting, connection.Unregistering:
|
||||
data.changeConnStatus(event, connTable, log, palette)
|
||||
case connection.SetURL:
|
||||
tunnelHostText.SetText(event.URL)
|
||||
data.edgeURL = event.URL
|
||||
case connection.RegisteringTunnel:
|
||||
if data.edgeURL == "" {
|
||||
tunnelHostText.SetText(fmt.Sprintf("Registering tunnel connection %d...", event.Index))
|
||||
}
|
||||
}
|
||||
app.Draw()
|
||||
})
|
||||
}
|
||||
|
||||
func NewDynamicColorTextView() *tview.TextView {
|
||||
return tview.NewTextView().SetDynamicColors(true)
|
||||
}
|
||||
|
||||
// Re-draws application when new logs are streamed to UI
|
||||
func handleNewText(app *tview.Application, logTextView *tview.TextView) func() {
|
||||
return func() {
|
||||
app.Draw()
|
||||
// SetFocus to enable scrolling in textview
|
||||
app.SetFocus(logTextView)
|
||||
}
|
||||
}
|
||||
|
||||
func (data *uiModel) changeConnStatus(event connection.Event, table *tview.Table, log *zerolog.Logger, palette palette) {
|
||||
index := int(event.Index)
|
||||
// Get connection location and state
|
||||
connState := data.getConnState(index)
|
||||
// Check if connection is already displayed in UI
|
||||
if connState == nil {
|
||||
log.Info().Msg("Connection is not in the UI table")
|
||||
return
|
||||
}
|
||||
|
||||
locationState := event.Location
|
||||
|
||||
if event.EventType == connection.Reconnecting {
|
||||
locationState = "Reconnecting..."
|
||||
}
|
||||
|
||||
connectionNum := index + 1
|
||||
// Get table cell
|
||||
cell := table.GetCell(index, 0)
|
||||
// Change dot color in front of text as well as location state
|
||||
text := newCellText(palette, connectionNum, locationState, event.EventType)
|
||||
cell.SetText(text)
|
||||
}
|
||||
|
||||
// Return connection location and row in UI table
|
||||
func (data *uiModel) getConnState(connID int) *connState {
|
||||
if connID < len(data.connections) {
|
||||
return &data.connections[connID]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (data *uiModel) setConnTableCell(event connection.Event, table *tview.Table, palette palette) {
|
||||
index := int(event.Index)
|
||||
connectionNum := index + 1
|
||||
|
||||
// Update slice to keep track of connection location and state in UI table
|
||||
data.connections[index].location = event.Location
|
||||
|
||||
// Update text in table cell to show disconnected state
|
||||
text := newCellText(palette, connectionNum, event.Location, event.EventType)
|
||||
cell := tview.NewTableCell(text)
|
||||
table.SetCell(index, 0, cell)
|
||||
}
|
||||
|
||||
func newCellText(palette palette, connectionNum int, location string, connectedStatus connection.Status) string {
|
||||
// HA connection indicator formatted as: "• #<CONNECTION_INDEX>: <COLO>",
|
||||
// where the left middle dot's color depends on the status of the connection
|
||||
const connFmtString = "[%s]\u2022[%s] #%d: %s"
|
||||
|
||||
var dotColor string
|
||||
switch connectedStatus {
|
||||
case connection.Connected:
|
||||
dotColor = palette.connected
|
||||
case connection.Disconnected:
|
||||
dotColor = palette.disconnected
|
||||
case connection.Reconnecting:
|
||||
dotColor = palette.reconnecting
|
||||
case connection.Unregistering:
|
||||
dotColor = palette.unregistered
|
||||
}
|
||||
|
||||
return fmt.Sprintf(connFmtString, dotColor, palette.defaultText, connectionNum, location)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import json
|
||||
import subprocess
|
||||
from time import sleep
|
||||
|
||||
from setup import get_config_from_file
|
||||
|
||||
SINGLE_CASE_TIMEOUT = 600
|
||||
|
||||
class CloudflaredCli:
|
||||
def __init__(self, config, config_path, logger):
|
||||
self.basecmd = [config.cloudflared_binary, "tunnel"]
|
||||
if config_path is not None:
|
||||
self.basecmd += ["--config", str(config_path)]
|
||||
origincert = get_config_from_file()["origincert"]
|
||||
if origincert:
|
||||
self.basecmd += ["--origincert", origincert]
|
||||
self.logger = logger
|
||||
|
||||
def _run_command(self, subcmd, subcmd_name, needs_to_pass=True):
|
||||
cmd = self.basecmd + subcmd
|
||||
# timeout limits the time a subprocess can run. This is useful to guard against running a tunnel when
|
||||
# command/args are in wrong order.
|
||||
result = run_subprocess(cmd, subcmd_name, self.logger, check=needs_to_pass, capture_output=True, timeout=15)
|
||||
return result
|
||||
|
||||
def list_tunnels(self):
|
||||
cmd_args = ["list", "--output", "json"]
|
||||
listed = self._run_command(cmd_args, "list")
|
||||
return json.loads(listed.stdout)
|
||||
|
||||
def get_tunnel_info(self, tunnel_id):
|
||||
info = self._run_command(["info", "--output", "json", tunnel_id], "info")
|
||||
return json.loads(info.stdout)
|
||||
|
||||
def __enter__(self):
|
||||
self.basecmd += ["run"]
|
||||
self.process = subprocess.Popen(self.basecmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
self.logger.info(f"Run cmd {self.basecmd}")
|
||||
return self.process
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||
terminate_gracefully(self.process, self.logger, self.basecmd)
|
||||
self.logger.debug(f"{self.basecmd} logs: {self.process.stderr.read()}")
|
||||
|
||||
|
||||
def terminate_gracefully(process, logger, cmd):
|
||||
process.terminate()
|
||||
process_terminated = wait_for_terminate(process)
|
||||
if not process_terminated:
|
||||
process.kill()
|
||||
logger.warning(f"{cmd}: cloudflared did not terminate within wait period. Killing process. logs: \
|
||||
stdout: {process.stdout.read()}, stderr: {process.stderr.read()}")
|
||||
|
||||
|
||||
def wait_for_terminate(opened_subprocess, attempts=10, poll_interval=1):
|
||||
"""
|
||||
wait_for_terminate polls the opened_subprocess every x seconds for a given number of attempts.
|
||||
It returns true if the subprocess was terminated and false if it didn't.
|
||||
"""
|
||||
for _ in range(attempts):
|
||||
if _is_process_stopped(opened_subprocess):
|
||||
return True
|
||||
sleep(poll_interval)
|
||||
return False
|
||||
|
||||
|
||||
def _is_process_stopped(process):
|
||||
return process.poll() is not None
|
||||
|
||||
|
||||
def cert_path():
|
||||
return get_config_from_file()["origincert"]
|
||||
|
||||
|
||||
class SubprocessError(Exception):
|
||||
def __init__(self, program, exit_code, cause):
|
||||
self.program = program
|
||||
self.exit_code = exit_code
|
||||
self.cause = cause
|
||||
|
||||
|
||||
def run_subprocess(cmd, cmd_name, logger, timeout=SINGLE_CASE_TIMEOUT, **kargs):
|
||||
kargs["timeout"] = timeout
|
||||
try:
|
||||
result = subprocess.run(cmd, **kargs)
|
||||
logger.debug(f"{cmd} log: {result.stdout}", extra={"cmd": cmd_name})
|
||||
return result
|
||||
except subprocess.CalledProcessError as e:
|
||||
err = f"{cmd} return exit code {e.returncode}, stderr" + e.stderr.decode("utf-8")
|
||||
logger.error(err, extra={"cmd": cmd_name, "return_code": e.returncode})
|
||||
raise SubprocessError(cmd[0], e.returncode, e)
|
||||
except subprocess.TimeoutExpired as e:
|
||||
err = f"{cmd} timeout after {e.timeout} seconds, stdout: {e.stdout}, stderr: {e.stderr}"
|
||||
logger.error(err, extra={"cmd": cmd_name, "return_code": "timeout"})
|
||||
raise e
|
||||
@@ -1,6 +1,7 @@
|
||||
METRICS_PORT = 51000
|
||||
MAX_RETRIES = 5
|
||||
BACKOFF_SECS = 7
|
||||
MAX_LOG_LINES = 50
|
||||
|
||||
PROXY_DNS_PORT = 9053
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
import ipaddress
|
||||
import socket
|
||||
|
||||
import pytest
|
||||
|
||||
from constants import protocols
|
||||
from cli import CloudflaredCli
|
||||
from util import get_tunnel_connector_id, LOGGER, wait_tunnel_ready, write_config
|
||||
|
||||
|
||||
class TestEdgeDiscovery:
|
||||
def _extra_config(self, protocol, edge_ip_version):
|
||||
config = {
|
||||
"protocol": protocol,
|
||||
}
|
||||
if edge_ip_version:
|
||||
config["edge-ip-version"] = edge_ip_version
|
||||
return config
|
||||
|
||||
@pytest.mark.parametrize("protocol", protocols())
|
||||
def test_default_only(self, tmp_path, component_tests_config, protocol):
|
||||
"""
|
||||
This test runs a tunnel to connect via IPv4-only edge addresses (default is unset "--edge-ip-version 4")
|
||||
"""
|
||||
if self.has_ipv6_only():
|
||||
pytest.skip("Host has IPv6 only support and current default is IPv4 only")
|
||||
self.expect_address_connections(
|
||||
tmp_path, component_tests_config, protocol, None, self.expect_ipv4_address)
|
||||
|
||||
@pytest.mark.parametrize("protocol", protocols())
|
||||
def test_ipv4_only(self, tmp_path, component_tests_config, protocol):
|
||||
"""
|
||||
This test runs a tunnel to connect via IPv4-only edge addresses
|
||||
"""
|
||||
if self.has_ipv6_only():
|
||||
pytest.skip("Host has IPv6 only support")
|
||||
self.expect_address_connections(
|
||||
tmp_path, component_tests_config, protocol, "4", self.expect_ipv4_address)
|
||||
|
||||
@pytest.mark.parametrize("protocol", protocols())
|
||||
def test_ipv6_only(self, tmp_path, component_tests_config, protocol):
|
||||
"""
|
||||
This test runs a tunnel to connect via IPv6-only edge addresses
|
||||
"""
|
||||
if self.has_ipv4_only():
|
||||
pytest.skip("Host has IPv4 only support")
|
||||
self.expect_address_connections(
|
||||
tmp_path, component_tests_config, protocol, "6", self.expect_ipv6_address)
|
||||
|
||||
@pytest.mark.parametrize("protocol", protocols())
|
||||
def test_auto_ip64(self, tmp_path, component_tests_config, protocol):
|
||||
"""
|
||||
This test runs a tunnel to connect via auto with a preference of IPv6 then IPv4 addresses for a dual stack host
|
||||
|
||||
This test also assumes that the host has IPv6 preference.
|
||||
"""
|
||||
if not self.has_dual_stack(address_family_preference=socket.AddressFamily.AF_INET6):
|
||||
pytest.skip("Host does not support dual stack with IPv6 preference")
|
||||
self.expect_address_connections(
|
||||
tmp_path, component_tests_config, protocol, "auto", self.expect_ipv6_address)
|
||||
|
||||
@pytest.mark.parametrize("protocol", protocols())
|
||||
def test_auto_ip46(self, tmp_path, component_tests_config, protocol):
|
||||
"""
|
||||
This test runs a tunnel to connect via auto with a preference of IPv4 then IPv6 addresses for a dual stack host
|
||||
|
||||
This test also assumes that the host has IPv4 preference.
|
||||
"""
|
||||
if not self.has_dual_stack(address_family_preference=socket.AddressFamily.AF_INET):
|
||||
pytest.skip("Host does not support dual stack with IPv4 preference")
|
||||
self.expect_address_connections(
|
||||
tmp_path, component_tests_config, protocol, "auto", self.expect_ipv4_address)
|
||||
|
||||
def expect_address_connections(self, tmp_path, component_tests_config, protocol, edge_ip_version, assert_address_type):
|
||||
config = component_tests_config(
|
||||
self._extra_config(protocol, edge_ip_version))
|
||||
config_path = write_config(tmp_path, config.full_config)
|
||||
LOGGER.debug(config)
|
||||
with CloudflaredCli(config, config_path, LOGGER):
|
||||
wait_tunnel_ready(tunnel_url=config.get_url(),
|
||||
require_min_connections=4)
|
||||
cfd_cli = CloudflaredCli(config, config_path, LOGGER)
|
||||
tunnel_id = config.get_tunnel_id()
|
||||
info = cfd_cli.get_tunnel_info(tunnel_id)
|
||||
connector_id = get_tunnel_connector_id()
|
||||
connector = next(
|
||||
(c for c in info["conns"] if c["id"] == connector_id), None)
|
||||
assert connector, f"Expected connection info from get tunnel info for the connected instance: {info}"
|
||||
conns = connector["conns"]
|
||||
assert conns == None or len(
|
||||
conns) == 4, f"There should be 4 connections registered: {conns}"
|
||||
for conn in conns:
|
||||
origin_ip = conn["origin_ip"]
|
||||
assert origin_ip, f"No available origin_ip for this connection: {conn}"
|
||||
assert_address_type(origin_ip)
|
||||
|
||||
def expect_ipv4_address(self, address):
|
||||
assert type(ipaddress.ip_address(
|
||||
address)) is ipaddress.IPv4Address, f"Expected connection from origin to be a valid IPv4 address: {address}"
|
||||
|
||||
def expect_ipv6_address(self, address):
|
||||
assert type(ipaddress.ip_address(
|
||||
address)) is ipaddress.IPv6Address, f"Expected connection from origin to be a valid IPv6 address: {address}"
|
||||
|
||||
def get_addresses(self):
|
||||
"""
|
||||
Returns a list of addresses for the host.
|
||||
"""
|
||||
host_addresses = socket.getaddrinfo(
|
||||
"region1.v2.argotunnel.com", 7844, socket.AF_UNSPEC, socket.SOCK_STREAM)
|
||||
assert len(
|
||||
host_addresses) > 0, "No addresses returned from getaddrinfo"
|
||||
return host_addresses
|
||||
|
||||
def has_dual_stack(self, address_family_preference=None):
|
||||
"""
|
||||
Returns true if the host has dual stack support and can optionally check
|
||||
the provided IP family preference.
|
||||
"""
|
||||
dual_stack = not self.has_ipv6_only() and not self.has_ipv4_only()
|
||||
if address_family_preference:
|
||||
address = self.get_addresses()[0]
|
||||
return dual_stack and address[0] == address_family_preference
|
||||
|
||||
return dual_stack
|
||||
|
||||
def has_ipv6_only(self):
|
||||
"""
|
||||
Returns True if the host has only IPv6 address support.
|
||||
"""
|
||||
return self.attempt_connection(socket.AddressFamily.AF_INET6) and not self.attempt_connection(socket.AddressFamily.AF_INET)
|
||||
|
||||
def has_ipv4_only(self):
|
||||
"""
|
||||
Returns True if the host has only IPv4 address support.
|
||||
"""
|
||||
return self.attempt_connection(socket.AddressFamily.AF_INET) and not self.attempt_connection(socket.AddressFamily.AF_INET6)
|
||||
|
||||
def attempt_connection(self, address_family):
|
||||
"""
|
||||
Returns True if a successful socket connection can be made to the
|
||||
remote host with the provided address family to validate host support
|
||||
for the provided address family.
|
||||
"""
|
||||
address = None
|
||||
for a in self.get_addresses():
|
||||
if a[0] == address_family:
|
||||
address = a
|
||||
break
|
||||
if address is None:
|
||||
# Couldn't even lookup the address family so we can't connect
|
||||
return False
|
||||
af, socktype, proto, canonname, sockaddr = address
|
||||
s = None
|
||||
try:
|
||||
s = socket.socket(af, socktype, proto)
|
||||
except OSError:
|
||||
return False
|
||||
try:
|
||||
s.connect(sockaddr)
|
||||
except OSError:
|
||||
s.close()
|
||||
return False
|
||||
s.close()
|
||||
return True
|
||||
@@ -2,6 +2,7 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from constants import MAX_LOG_LINES
|
||||
from util import start_cloudflared, wait_tunnel_ready, send_requests
|
||||
|
||||
# Rolling logger rotate log files after 1 MB
|
||||
@@ -11,14 +12,24 @@ expect_message = "Starting Hello"
|
||||
|
||||
|
||||
def assert_log_to_terminal(cloudflared):
|
||||
stderr = cloudflared.stderr.read(1500)
|
||||
assert expect_message.encode() in stderr, f"{stderr} doesn't contain {expect_message}"
|
||||
for _ in range(0, MAX_LOG_LINES):
|
||||
line = cloudflared.stderr.readline()
|
||||
if not line:
|
||||
break
|
||||
if expect_message.encode() in line:
|
||||
return
|
||||
raise Exception(f"terminal log doesn't contain {expect_message}")
|
||||
|
||||
|
||||
def assert_log_in_file(file):
|
||||
with open(file, "r") as f:
|
||||
log = f.read(2000)
|
||||
assert expect_message in log, f"{log} doesn't contain {expect_message}"
|
||||
for _ in range(0, MAX_LOG_LINES):
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
if expect_message in line:
|
||||
return
|
||||
raise Exception(f"log file doesn't contain {expect_message}")
|
||||
|
||||
|
||||
def assert_json_log(file):
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
from util import LOGGER, nofips, start_cloudflared, wait_tunnel_ready
|
||||
|
||||
|
||||
@nofips
|
||||
class TestPostQuantum:
|
||||
def _extra_config(self):
|
||||
config = {
|
||||
"protocol": "quic",
|
||||
}
|
||||
return config
|
||||
|
||||
def test_post_quantum(self, tmp_path, component_tests_config):
|
||||
config = component_tests_config(self._extra_config())
|
||||
LOGGER.debug(config)
|
||||
with start_cloudflared(tmp_path, config, cfd_args=["run", "--post-quantum"], new_process=True):
|
||||
wait_tunnel_ready(tunnel_url=config.get_url(),
|
||||
require_min_connections=4)
|
||||
@@ -12,18 +12,12 @@ class TestProxyDns:
|
||||
def test_proxy_dns_with_named_tunnel(self, tmp_path, component_tests_config):
|
||||
run_test_scenario(tmp_path, component_tests_config, CfdModes.NAMED, run_proxy_dns=True)
|
||||
|
||||
def test_proxy_dns_with_classic_tunnel(self, tmp_path, component_tests_config):
|
||||
run_test_scenario(tmp_path, component_tests_config, CfdModes.CLASSIC, run_proxy_dns=True)
|
||||
|
||||
def test_proxy_dns_alone(self, tmp_path, component_tests_config):
|
||||
run_test_scenario(tmp_path, component_tests_config, CfdModes.PROXY_DNS, run_proxy_dns=True)
|
||||
|
||||
def test_named_tunnel_alone(self, tmp_path, component_tests_config):
|
||||
run_test_scenario(tmp_path, component_tests_config, CfdModes.NAMED, run_proxy_dns=False)
|
||||
|
||||
def test_classic_tunnel_alone(self, tmp_path, component_tests_config):
|
||||
run_test_scenario(tmp_path, component_tests_config, CfdModes.CLASSIC, run_proxy_dns=False)
|
||||
|
||||
|
||||
def run_test_scenario(tmp_path, component_tests_config, cfd_mode, run_proxy_dns):
|
||||
expect_proxy_dns = run_proxy_dns
|
||||
@@ -33,10 +27,6 @@ def run_test_scenario(tmp_path, component_tests_config, cfd_mode, run_proxy_dns)
|
||||
expect_tunnel = True
|
||||
pre_args = ["tunnel"]
|
||||
args = ["run"]
|
||||
elif cfd_mode == CfdModes.CLASSIC:
|
||||
expect_tunnel = True
|
||||
pre_args = []
|
||||
args = []
|
||||
elif cfd_mode == CfdModes.PROXY_DNS:
|
||||
expect_proxy_dns = True
|
||||
pre_args = []
|
||||
|
||||
@@ -33,13 +33,6 @@ class TestReconnect:
|
||||
# Repeat the test multiple times because some issues only occur after multiple reconnects
|
||||
self.assert_reconnect(config, cloudflared, 5)
|
||||
|
||||
def test_classic_reconnect(self, tmp_path, component_tests_config):
|
||||
extra_config = copy.copy(self.extra_config)
|
||||
extra_config["hello-world"] = True
|
||||
config = component_tests_config(additional_config=extra_config, cfd_mode=CfdModes.CLASSIC)
|
||||
with start_cloudflared(tmp_path, config, cfd_args=[], new_process=True, allow_input=True, capture_output=False) as cloudflared:
|
||||
self.assert_reconnect(config, cloudflared, 1)
|
||||
|
||||
def send_reconnect(self, cloudflared, secs):
|
||||
# Although it is recommended to use the Popen.communicate method, we cannot
|
||||
# use it because it blocks on reading stdout and stderr until EOF is reached
|
||||
@@ -47,17 +40,12 @@ class TestReconnect:
|
||||
cloudflared.stdin.flush()
|
||||
|
||||
def assert_reconnect(self, config, cloudflared, repeat):
|
||||
wait_tunnel_ready(tunnel_url=config.get_url(), require_min_connections=self.default_ha_conns)
|
||||
wait_tunnel_ready(tunnel_url=config.get_url(),
|
||||
require_min_connections=self.default_ha_conns)
|
||||
for _ in range(repeat):
|
||||
for i in range(self.default_ha_conns):
|
||||
for _ in range(self.default_ha_conns):
|
||||
self.send_reconnect(cloudflared, self.default_reconnect_secs)
|
||||
expect_connections = self.default_ha_conns-i-1
|
||||
if expect_connections > 0:
|
||||
# Don't check if tunnel returns 200 here because there is a race condition between wait_tunnel_ready
|
||||
# retrying to get 200 response and reconnecting
|
||||
wait_tunnel_ready(require_min_connections=expect_connections)
|
||||
else:
|
||||
check_tunnel_not_connected()
|
||||
|
||||
check_tunnel_not_connected()
|
||||
sleep(self.default_reconnect_secs * 2)
|
||||
wait_tunnel_ready(tunnel_url=config.get_url(), require_min_connections=self.default_ha_conns)
|
||||
wait_tunnel_ready(tunnel_url=config.get_url(),
|
||||
require_min_connections=self.default_ha_conns)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import pathlib
|
||||
import platform
|
||||
import subprocess
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
@@ -10,12 +9,7 @@ import pytest
|
||||
|
||||
import test_logging
|
||||
from conftest import CfdModes
|
||||
from util import start_cloudflared, wait_tunnel_ready, write_config
|
||||
|
||||
|
||||
def select_platform(plat):
|
||||
return pytest.mark.skipif(
|
||||
platform.system() != plat, reason=f"Only runs on {plat}")
|
||||
from util import select_platform, start_cloudflared, wait_tunnel_ready, write_config
|
||||
|
||||
|
||||
def default_config_dir():
|
||||
|
||||
+34
-5
@@ -1,9 +1,12 @@
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
from contextlib import contextmanager
|
||||
from time import sleep
|
||||
|
||||
import pytest
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
from retrying import retry
|
||||
@@ -13,6 +16,17 @@ from constants import METRICS_PORT, MAX_RETRIES, BACKOFF_SECS
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def select_platform(plat):
|
||||
return pytest.mark.skipif(
|
||||
platform.system() != plat, reason=f"Only runs on {plat}")
|
||||
|
||||
def fips_enabled():
|
||||
env_fips = os.getenv("COMPONENT_TESTS_FIPS")
|
||||
return env_fips is not None and env_fips != "0"
|
||||
|
||||
nofips = pytest.mark.skipif(
|
||||
fips_enabled(), reason=f"Only runs without FIPS (COMPONENT_TESTS_FIPS=0)")
|
||||
|
||||
def write_config(directory, config):
|
||||
config_path = directory / "config.yml"
|
||||
with open(config_path, 'w') as outfile:
|
||||
@@ -54,13 +68,15 @@ def cloudflared_cmd(config, config_path, cfd_args, cfd_pre_args, root):
|
||||
def run_cloudflared_background(cmd, allow_input, capture_output):
|
||||
output = subprocess.PIPE if capture_output else subprocess.DEVNULL
|
||||
stdin = subprocess.PIPE if allow_input else None
|
||||
cfd = None
|
||||
try:
|
||||
cfd = subprocess.Popen(cmd, stdin=stdin, stdout=output, stderr=output)
|
||||
yield cfd
|
||||
finally:
|
||||
cfd.terminate()
|
||||
if capture_output:
|
||||
LOGGER.info(f"cloudflared log: {cfd.stderr.read()}")
|
||||
if cfd:
|
||||
cfd.terminate()
|
||||
if capture_output:
|
||||
LOGGER.info(f"cloudflared log: {cfd.stderr.read()}")
|
||||
|
||||
|
||||
def wait_tunnel_ready(tunnel_url=None, require_min_connections=1, cfd_logs=None):
|
||||
@@ -99,18 +115,31 @@ def _log_cloudflared_logs(cfd_logs):
|
||||
LOGGER.warning(line)
|
||||
|
||||
|
||||
@retry(stop_max_attempt_number=MAX_RETRIES * BACKOFF_SECS, wait_fixed=1000)
|
||||
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
||||
def check_tunnel_not_connected():
|
||||
url = f'http://localhost:{METRICS_PORT}/ready'
|
||||
|
||||
try:
|
||||
resp = requests.get(url, timeout=1)
|
||||
resp = requests.get(url, timeout=BACKOFF_SECS)
|
||||
assert resp.status_code == 503, f"Expect {url} returns 503, got {resp.status_code}"
|
||||
assert resp.json()[
|
||||
"readyConnections"] == 0, "Expected all connections to be terminated (pending reconnect)"
|
||||
# cloudflared might already terminate
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
LOGGER.warning(f"Failed to connect to {url}, error: {e}")
|
||||
|
||||
|
||||
def get_tunnel_connector_id():
|
||||
url = f'http://localhost:{METRICS_PORT}/ready'
|
||||
|
||||
try:
|
||||
resp = requests.get(url, timeout=1)
|
||||
return resp.json()["connectorId"]
|
||||
# cloudflared might already terminated
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
LOGGER.warning(f"Failed to connect to {url}, error: {e}")
|
||||
|
||||
|
||||
# In some cases we don't need to check response status, such as when sending batch requests to generate logs
|
||||
def send_requests(url, count, require_ok=True):
|
||||
errors = 0
|
||||
|
||||
+41
-23
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudflare/cloudflared/validation"
|
||||
)
|
||||
@@ -177,9 +177,9 @@ func ValidateUrl(c *cli.Context, allowURLFromArgs bool) (*url.URL, error) {
|
||||
}
|
||||
|
||||
type UnvalidatedIngressRule struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Path string `json:"path"`
|
||||
Service string `json:"service"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Service string `json:"service,omitempty"`
|
||||
OriginRequest OriginRequestConfig `yaml:"originRequest" json:"originRequest"`
|
||||
}
|
||||
|
||||
@@ -192,41 +192,56 @@ type UnvalidatedIngressRule struct {
|
||||
// - To specify a time.Duration in json, use int64 of the nanoseconds
|
||||
type OriginRequestConfig struct {
|
||||
// HTTP proxy timeout for establishing a new connection
|
||||
ConnectTimeout *CustomDuration `yaml:"connectTimeout" json:"connectTimeout"`
|
||||
ConnectTimeout *CustomDuration `yaml:"connectTimeout" json:"connectTimeout,omitempty"`
|
||||
// HTTP proxy timeout for completing a TLS handshake
|
||||
TLSTimeout *CustomDuration `yaml:"tlsTimeout" json:"tlsTimeout"`
|
||||
TLSTimeout *CustomDuration `yaml:"tlsTimeout" json:"tlsTimeout,omitempty"`
|
||||
// HTTP proxy TCP keepalive duration
|
||||
TCPKeepAlive *CustomDuration `yaml:"tcpKeepAlive" json:"tcpKeepAlive"`
|
||||
TCPKeepAlive *CustomDuration `yaml:"tcpKeepAlive" json:"tcpKeepAlive,omitempty"`
|
||||
// HTTP proxy should disable "happy eyeballs" for IPv4/v6 fallback
|
||||
NoHappyEyeballs *bool `yaml:"noHappyEyeballs" json:"noHappyEyeballs"`
|
||||
NoHappyEyeballs *bool `yaml:"noHappyEyeballs" json:"noHappyEyeballs,omitempty"`
|
||||
// HTTP proxy maximum keepalive connection pool size
|
||||
KeepAliveConnections *int `yaml:"keepAliveConnections" json:"keepAliveConnections"`
|
||||
KeepAliveConnections *int `yaml:"keepAliveConnections" json:"keepAliveConnections,omitempty"`
|
||||
// HTTP proxy timeout for closing an idle connection
|
||||
KeepAliveTimeout *CustomDuration `yaml:"keepAliveTimeout" json:"keepAliveTimeout"`
|
||||
KeepAliveTimeout *CustomDuration `yaml:"keepAliveTimeout" json:"keepAliveTimeout,omitempty"`
|
||||
// Sets the HTTP Host header for the local webserver.
|
||||
HTTPHostHeader *string `yaml:"httpHostHeader" json:"httpHostHeader"`
|
||||
HTTPHostHeader *string `yaml:"httpHostHeader" json:"httpHostHeader,omitempty"`
|
||||
// Hostname on the origin server certificate.
|
||||
OriginServerName *string `yaml:"originServerName" json:"originServerName"`
|
||||
OriginServerName *string `yaml:"originServerName" json:"originServerName,omitempty"`
|
||||
// Path to the CA for the certificate of your origin.
|
||||
// This option should be used only if your certificate is not signed by Cloudflare.
|
||||
CAPool *string `yaml:"caPool" json:"caPool"`
|
||||
CAPool *string `yaml:"caPool" json:"caPool,omitempty"`
|
||||
// Disables TLS verification of the certificate presented by your origin.
|
||||
// Will allow any certificate from the origin to be accepted.
|
||||
// Note: The connection from your machine to Cloudflare's Edge is still encrypted.
|
||||
NoTLSVerify *bool `yaml:"noTLSVerify" json:"noTLSVerify"`
|
||||
NoTLSVerify *bool `yaml:"noTLSVerify" json:"noTLSVerify,omitempty"`
|
||||
// Disables chunked transfer encoding.
|
||||
// Useful if you are running a WSGI server.
|
||||
DisableChunkedEncoding *bool `yaml:"disableChunkedEncoding" json:"disableChunkedEncoding"`
|
||||
DisableChunkedEncoding *bool `yaml:"disableChunkedEncoding" json:"disableChunkedEncoding,omitempty"`
|
||||
// Runs as jump host
|
||||
BastionMode *bool `yaml:"bastionMode" json:"bastionMode"`
|
||||
BastionMode *bool `yaml:"bastionMode" json:"bastionMode,omitempty"`
|
||||
// Listen address for the proxy.
|
||||
ProxyAddress *string `yaml:"proxyAddress" json:"proxyAddress"`
|
||||
ProxyAddress *string `yaml:"proxyAddress" json:"proxyAddress,omitempty"`
|
||||
// Listen port for the proxy.
|
||||
ProxyPort *uint `yaml:"proxyPort" json:"proxyPort"`
|
||||
ProxyPort *uint `yaml:"proxyPort" json:"proxyPort,omitempty"`
|
||||
// Valid options are 'socks' or empty.
|
||||
ProxyType *string `yaml:"proxyType" json:"proxyType"`
|
||||
ProxyType *string `yaml:"proxyType" json:"proxyType,omitempty"`
|
||||
// IP rules for the proxy service
|
||||
IPRules []IngressIPRule `yaml:"ipRules" json:"ipRules"`
|
||||
IPRules []IngressIPRule `yaml:"ipRules" json:"ipRules,omitempty"`
|
||||
// Attempt to connect to origin with HTTP/2
|
||||
Http2Origin *bool `yaml:"http2Origin" json:"http2Origin,omitempty"`
|
||||
// Access holds all access related configs
|
||||
Access *AccessConfig `yaml:"access" json:"access,omitempty"`
|
||||
}
|
||||
|
||||
type AccessConfig struct {
|
||||
// Required when set to true will fail every request that does not arrive through an access authenticated endpoint.
|
||||
Required bool `yaml:"required" json:"required,omitempty"`
|
||||
|
||||
// TeamName is the organization team name to get the public key certificates for.
|
||||
TeamName string `yaml:"teamName" json:"teamName"`
|
||||
|
||||
// AudTag is the AudTag to verify access JWT against.
|
||||
AudTag []string `yaml:"audTag" json:"audTag"`
|
||||
}
|
||||
|
||||
type IngressIPRule struct {
|
||||
@@ -244,7 +259,9 @@ type Configuration struct {
|
||||
}
|
||||
|
||||
type WarpRoutingConfig struct {
|
||||
Enabled bool `yaml:"enabled" json:"enabled"`
|
||||
Enabled bool `yaml:"enabled" json:"enabled"`
|
||||
ConnectTimeout *CustomDuration `yaml:"connectTimeout" json:"connectTimeout,omitempty"`
|
||||
TCPKeepAlive *CustomDuration `yaml:"tcpKeepAlive" json:"tcpKeepAlive,omitempty"`
|
||||
}
|
||||
|
||||
type configFileSettings struct {
|
||||
@@ -374,7 +391,8 @@ func ReadConfigFile(c *cli.Context, log *zerolog.Logger) (settings *configFileSe
|
||||
log.Debug().Msgf("Loading configuration from %s", configFile)
|
||||
file, err := os.Open(configFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// If does not exist and config file was not specificly specified then return ErrNoConfigFile found.
|
||||
if os.IsNotExist(err) && !c.IsSet("config") {
|
||||
err = ErrNoConfigFile
|
||||
}
|
||||
return nil, "", err
|
||||
@@ -392,7 +410,7 @@ func ReadConfigFile(c *cli.Context, log *zerolog.Logger) (settings *configFileSe
|
||||
// Parse it again, with strict mode, to find warnings.
|
||||
if file, err := os.Open(configFile); err == nil {
|
||||
decoder := yaml.NewDecoder(file)
|
||||
decoder.SetStrict(true)
|
||||
decoder.KnownFields(true)
|
||||
var unusedConfig configFileSettings
|
||||
if err := decoder.Decode(&unusedConfig); err != nil {
|
||||
warnings = err.Error()
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestConfigFileSettings(t *testing.T) {
|
||||
@@ -23,7 +23,9 @@ func TestConfigFileSettings(t *testing.T) {
|
||||
Service: "https://localhost:8001",
|
||||
}
|
||||
warpRouting = WarpRoutingConfig{
|
||||
Enabled: true,
|
||||
Enabled: true,
|
||||
ConnectTimeout: &CustomDuration{Duration: 2 * time.Second},
|
||||
TCPKeepAlive: &CustomDuration{Duration: 10 * time.Second},
|
||||
}
|
||||
)
|
||||
rawYAML := `
|
||||
@@ -48,6 +50,9 @@ ingress:
|
||||
service: https://localhost:8001
|
||||
warp-routing:
|
||||
enabled: true
|
||||
connectTimeout: 2s
|
||||
tcpKeepAlive: 10s
|
||||
|
||||
retries: 5
|
||||
grace-period: 30s
|
||||
percentage: 3.14
|
||||
@@ -111,7 +116,7 @@ counters:
|
||||
|
||||
}
|
||||
|
||||
var rawConfig = []byte(`
|
||||
var rawJsonConfig = []byte(`
|
||||
{
|
||||
"connectTimeout": 10,
|
||||
"tlsTimeout": 30,
|
||||
@@ -139,7 +144,8 @@ var rawConfig = []byte(`
|
||||
"ports": [443, 4443],
|
||||
"allow": true
|
||||
}
|
||||
]
|
||||
],
|
||||
"http2Origin": true
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -148,15 +154,14 @@ func TestMarshalUnmarshalOriginRequest(t *testing.T) {
|
||||
name string
|
||||
marshalFunc func(in interface{}) (out []byte, err error)
|
||||
unMarshalFunc func(in []byte, out interface{}) (err error)
|
||||
baseUnit time.Duration
|
||||
}{
|
||||
{"json", json.Marshal, json.Unmarshal, time.Second},
|
||||
{"yaml", yaml.Marshal, yaml.Unmarshal, time.Nanosecond},
|
||||
{"json", json.Marshal, json.Unmarshal},
|
||||
{"yaml", yaml.Marshal, yaml.Unmarshal},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assertConfig(t, tc.marshalFunc, tc.unMarshalFunc, tc.baseUnit)
|
||||
assertConfig(t, tc.marshalFunc, tc.unMarshalFunc)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -165,18 +170,17 @@ func assertConfig(
|
||||
t *testing.T,
|
||||
marshalFunc func(in interface{}) (out []byte, err error),
|
||||
unMarshalFunc func(in []byte, out interface{}) (err error),
|
||||
baseUnit time.Duration,
|
||||
) {
|
||||
var config OriginRequestConfig
|
||||
var config2 OriginRequestConfig
|
||||
|
||||
assert.NoError(t, unMarshalFunc(rawConfig, &config))
|
||||
assert.NoError(t, json.Unmarshal(rawJsonConfig, &config))
|
||||
|
||||
assert.Equal(t, baseUnit*10, config.ConnectTimeout.Duration)
|
||||
assert.Equal(t, baseUnit*30, config.TLSTimeout.Duration)
|
||||
assert.Equal(t, baseUnit*30, config.TCPKeepAlive.Duration)
|
||||
assert.Equal(t, time.Second*10, config.ConnectTimeout.Duration)
|
||||
assert.Equal(t, time.Second*30, config.TLSTimeout.Duration)
|
||||
assert.Equal(t, time.Second*30, config.TCPKeepAlive.Duration)
|
||||
assert.Equal(t, true, *config.NoHappyEyeballs)
|
||||
assert.Equal(t, baseUnit*60, config.KeepAliveTimeout.Duration)
|
||||
assert.Equal(t, time.Second*60, config.KeepAliveTimeout.Duration)
|
||||
assert.Equal(t, 10, *config.KeepAliveConnections)
|
||||
assert.Equal(t, "app.tunnel.com", *config.HTTPHostHeader)
|
||||
assert.Equal(t, "app.tunnel.com", *config.OriginServerName)
|
||||
@@ -188,6 +192,7 @@ func assertConfig(
|
||||
assert.Equal(t, true, *config.NoTLSVerify)
|
||||
assert.Equal(t, uint(9000), *config.ProxyPort)
|
||||
assert.Equal(t, "socks", *config.ProxyType)
|
||||
assert.Equal(t, true, *config.Http2Origin)
|
||||
|
||||
privateV4 := "10.0.0.0/8"
|
||||
privateV6 := "fc00::/7"
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudflare/cloudflared/watcher"
|
||||
)
|
||||
|
||||
+35
-10
@@ -24,13 +24,22 @@ const (
|
||||
LogFieldConnIndex = "connIndex"
|
||||
MaxGracePeriod = time.Minute * 3
|
||||
MaxConcurrentStreams = math.MaxUint32
|
||||
|
||||
contentTypeHeader = "content-type"
|
||||
sseContentType = "text/event-stream"
|
||||
grpcContentType = "application/grpc"
|
||||
)
|
||||
|
||||
var switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols, http.StatusText(http.StatusSwitchingProtocols))
|
||||
var (
|
||||
switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols, http.StatusText(http.StatusSwitchingProtocols))
|
||||
flushableContentTypes = []string{sseContentType, grpcContentType}
|
||||
)
|
||||
|
||||
type Orchestrator interface {
|
||||
UpdateConfig(version int32, config []byte) *pogs.UpdateConfigurationResponse
|
||||
GetConfigJSON() ([]byte, error)
|
||||
GetOriginProxy() (OriginProxy, error)
|
||||
WarpRoutingEnabled() (enabled bool)
|
||||
}
|
||||
|
||||
type NamedTunnelProperties struct {
|
||||
@@ -122,22 +131,24 @@ func (t Type) String() string {
|
||||
|
||||
// OriginProxy is how data flows from cloudflared to the origin services running behind it.
|
||||
type OriginProxy interface {
|
||||
ProxyHTTP(w ResponseWriter, tr *tracing.TracedRequest, isWebsocket bool) error
|
||||
ProxyHTTP(w ResponseWriter, tr *tracing.TracedHTTPRequest, isWebsocket bool) error
|
||||
ProxyTCP(ctx context.Context, rwa ReadWriteAcker, req *TCPRequest) error
|
||||
}
|
||||
|
||||
// TCPRequest defines the input format needed to perform a TCP proxy.
|
||||
type TCPRequest struct {
|
||||
Dest string
|
||||
CFRay string
|
||||
LBProbe bool
|
||||
Dest string
|
||||
CFRay string
|
||||
LBProbe bool
|
||||
FlowID string
|
||||
CfTraceID string
|
||||
}
|
||||
|
||||
// ReadWriteAcker is a readwriter with the ability to Acknowledge to the downstream (edge) that the origin has
|
||||
// accepted the connection.
|
||||
type ReadWriteAcker interface {
|
||||
io.ReadWriter
|
||||
AckConnection() error
|
||||
AckConnection(tracePropagation string) error
|
||||
}
|
||||
|
||||
// HTTPResponseReadWriteAcker is an HTTP implementation of ReadWriteAcker.
|
||||
@@ -166,22 +177,28 @@ func (h *HTTPResponseReadWriteAcker) Write(p []byte) (int, error) {
|
||||
|
||||
// AckConnection acks an HTTP connection by sending a switch protocols status code that enables the caller to
|
||||
// upgrade to streams.
|
||||
func (h *HTTPResponseReadWriteAcker) AckConnection() error {
|
||||
func (h *HTTPResponseReadWriteAcker) AckConnection(tracePropagation string) error {
|
||||
resp := &http.Response{
|
||||
Status: switchingProtocolText,
|
||||
StatusCode: http.StatusSwitchingProtocols,
|
||||
ContentLength: -1,
|
||||
Header: http.Header{},
|
||||
}
|
||||
|
||||
if secWebsocketKey := h.req.Header.Get("Sec-WebSocket-Key"); secWebsocketKey != "" {
|
||||
resp.Header = websocket.NewResponseHeader(h.req)
|
||||
}
|
||||
|
||||
if tracePropagation != "" {
|
||||
resp.Header.Add(tracing.CanonicalCloudflaredTracingHeader, tracePropagation)
|
||||
}
|
||||
|
||||
return h.w.WriteRespHeaders(resp.StatusCode, resp.Header)
|
||||
}
|
||||
|
||||
type ResponseWriter interface {
|
||||
WriteRespHeaders(status int, header http.Header) error
|
||||
AddTrailer(trailerName, trailerValue string)
|
||||
io.Writer
|
||||
}
|
||||
|
||||
@@ -190,10 +207,18 @@ type ConnectedFuse interface {
|
||||
IsConnected() bool
|
||||
}
|
||||
|
||||
func IsServerSentEvent(headers http.Header) bool {
|
||||
if contentType := headers.Get("content-type"); contentType != "" {
|
||||
return strings.HasPrefix(strings.ToLower(contentType), "text/event-stream")
|
||||
// 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 {
|
||||
if contentType := headers.Get(contentTypeHeader); contentType != "" {
|
||||
contentType = strings.ToLower(contentType)
|
||||
for _, c := range flushableContentTypes {
|
||||
if strings.HasPrefix(contentType, c) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,11 @@ import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/cloudflare/cloudflared/stream"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
@@ -30,6 +29,8 @@ var (
|
||||
testLargeResp = make([]byte, largeFileSize)
|
||||
)
|
||||
|
||||
var _ ReadWriteAcker = (*HTTPResponseReadWriteAcker)(nil)
|
||||
|
||||
type testRequest struct {
|
||||
name string
|
||||
endpoint string
|
||||
@@ -42,6 +43,10 @@ type mockOrchestrator struct {
|
||||
originProxy OriginProxy
|
||||
}
|
||||
|
||||
func (mcr *mockOrchestrator) GetConfigJSON() ([]byte, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (*mockOrchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.UpdateConfigurationResponse {
|
||||
return &tunnelpogs.UpdateConfigurationResponse{
|
||||
LastAppliedVersion: version,
|
||||
@@ -52,11 +57,15 @@ func (mcr *mockOrchestrator) GetOriginProxy() (OriginProxy, error) {
|
||||
return mcr.originProxy, nil
|
||||
}
|
||||
|
||||
func (mcr *mockOrchestrator) WarpRoutingEnabled() (enabled bool) {
|
||||
return true
|
||||
}
|
||||
|
||||
type mockOriginProxy struct{}
|
||||
|
||||
func (moc *mockOriginProxy) ProxyHTTP(
|
||||
w ResponseWriter,
|
||||
tr *tracing.TracedRequest,
|
||||
tr *tracing.TracedHTTPRequest,
|
||||
isWebsocket bool,
|
||||
) error {
|
||||
req := tr.Request
|
||||
@@ -132,7 +141,7 @@ func wsEchoEndpoint(w ResponseWriter, r *http.Request) error {
|
||||
}()
|
||||
|
||||
originConn := &echoPipe{reader: readPipe, writer: writePipe}
|
||||
websocket.Stream(wsConn, originConn, &log)
|
||||
stream.Pipe(wsConn, originConn, &log)
|
||||
cancel()
|
||||
wsConn.Close()
|
||||
return nil
|
||||
@@ -170,7 +179,7 @@ func wsFlakyEndpoint(w ResponseWriter, r *http.Request) error {
|
||||
|
||||
closedAfter := time.Millisecond * time.Duration(rand.Intn(50))
|
||||
originConn := &flakyConn{closeAt: time.Now().Add(closedAfter)}
|
||||
websocket.Stream(wsConn, originConn, &log)
|
||||
stream.Pipe(wsConn, originConn, &log)
|
||||
cancel()
|
||||
wsConn.Close()
|
||||
return nil
|
||||
@@ -191,40 +200,3 @@ func (mcf mockConnectedFuse) Connected() {}
|
||||
func (mcf mockConnectedFuse) IsConnected() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func TestIsEventStream(t *testing.T) {
|
||||
tests := []struct {
|
||||
headers http.Header
|
||||
isEventStream bool
|
||||
}{
|
||||
{
|
||||
headers: newHeader("Content-Type", "text/event-stream"),
|
||||
isEventStream: true,
|
||||
},
|
||||
{
|
||||
headers: newHeader("content-type", "text/event-stream"),
|
||||
isEventStream: true,
|
||||
},
|
||||
{
|
||||
headers: newHeader("Content-Type", "text/event-stream; charset=utf-8"),
|
||||
isEventStream: true,
|
||||
},
|
||||
{
|
||||
headers: newHeader("Content-Type", "application/json"),
|
||||
isEventStream: false,
|
||||
},
|
||||
{
|
||||
headers: http.Header{},
|
||||
isEventStream: false,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
assert.Equal(t, test.isEventStream, IsServerSentEvent(test.headers))
|
||||
}
|
||||
}
|
||||
|
||||
func newHeader(key, value string) http.Header {
|
||||
header := http.Header{}
|
||||
header.Add(key, value)
|
||||
return header
|
||||
}
|
||||
|
||||
+30
-2
@@ -2,7 +2,9 @@ package connection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
@@ -19,6 +21,8 @@ type controlStream struct {
|
||||
connectedFuse ConnectedFuse
|
||||
namedTunnelProperties *NamedTunnelProperties
|
||||
connIndex uint8
|
||||
edgeAddress net.IP
|
||||
protocol Protocol
|
||||
|
||||
newRPCClientFunc RPCClientFunc
|
||||
|
||||
@@ -30,20 +34,26 @@ 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) error
|
||||
ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error
|
||||
// IsStopped tells whether the method above has finished
|
||||
IsStopped() bool
|
||||
}
|
||||
|
||||
type TunnelConfigJSONGetter interface {
|
||||
GetConfigJSON() ([]byte, error)
|
||||
}
|
||||
|
||||
// NewControlStream returns a new instance of ControlStreamHandler
|
||||
func NewControlStream(
|
||||
observer *Observer,
|
||||
connectedFuse ConnectedFuse,
|
||||
namedTunnelConfig *NamedTunnelProperties,
|
||||
connIndex uint8,
|
||||
edgeAddress net.IP,
|
||||
newRPCClientFunc RPCClientFunc,
|
||||
gracefulShutdownC <-chan struct{},
|
||||
gracePeriod time.Duration,
|
||||
protocol Protocol,
|
||||
) ControlStreamHandler {
|
||||
if newRPCClientFunc == nil {
|
||||
newRPCClientFunc = newRegistrationRPCClient
|
||||
@@ -54,8 +64,10 @@ func NewControlStream(
|
||||
namedTunnelProperties: namedTunnelConfig,
|
||||
newRPCClientFunc: newRPCClientFunc,
|
||||
connIndex: connIndex,
|
||||
edgeAddress: edgeAddress,
|
||||
gracefulShutdownC: gracefulShutdownC,
|
||||
gracePeriod: gracePeriod,
|
||||
protocol: protocol,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,15 +75,31 @@ func (c *controlStream) ServeControlStream(
|
||||
ctx context.Context,
|
||||
rw io.ReadWriteCloser,
|
||||
connOptions *tunnelpogs.ConnectionOptions,
|
||||
tunnelConfigGetter TunnelConfigJSONGetter,
|
||||
) error {
|
||||
rpcClient := c.newRPCClientFunc(ctx, rw, c.observer.log)
|
||||
|
||||
if err := rpcClient.RegisterConnection(ctx, c.namedTunnelProperties, connOptions, c.connIndex, c.observer); err != nil {
|
||||
registrationDetails, err := rpcClient.RegisterConnection(ctx, c.namedTunnelProperties, connOptions, c.connIndex, c.edgeAddress, c.observer)
|
||||
if err != nil {
|
||||
rpcClient.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
c.observer.logServerInfo(c.connIndex, registrationDetails.Location, c.edgeAddress, fmt.Sprintf("Connection %s registered with protocol: %s", registrationDetails.UUID, c.protocol))
|
||||
c.observer.sendConnectedEvent(c.connIndex, c.protocol, registrationDetails.Location)
|
||||
c.connectedFuse.Connected()
|
||||
|
||||
// if conn index is 0 and tunnel is not remotely managed, then send local ingress rules configuration
|
||||
if c.connIndex == 0 && !registrationDetails.TunnelIsRemotelyManaged {
|
||||
if tunnelConfig, err := tunnelConfigGetter.GetConfigJSON(); err == nil {
|
||||
if err := rpcClient.SendLocalConfiguration(ctx, tunnelConfig, c.observer); err != nil {
|
||||
c.observer.log.Err(err).Msg("unable to send local configuration")
|
||||
}
|
||||
} else {
|
||||
c.observer.log.Err(err).Msg("failed to obtain current configuration")
|
||||
}
|
||||
}
|
||||
|
||||
c.waitForUnregister(ctx, rpcClient)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,6 +18,19 @@ func (e DupConnRegisterTunnelError) Error() string {
|
||||
return "already connected to this server, trying another address"
|
||||
}
|
||||
|
||||
// Dial to edge server with quic failed
|
||||
type EdgeQuicDialError struct {
|
||||
Cause error
|
||||
}
|
||||
|
||||
func (e *EdgeQuicDialError) Error() string {
|
||||
return "failed to dial to edge with quic: " + e.Cause.Error()
|
||||
}
|
||||
|
||||
func (e *EdgeQuicDialError) Unwrap() error {
|
||||
return e.Cause
|
||||
}
|
||||
|
||||
// RegisterTunnel error from server
|
||||
type ServerRegisterTunnelError struct {
|
||||
Cause error
|
||||
|
||||
@@ -5,6 +5,7 @@ type Event struct {
|
||||
Index uint8
|
||||
EventType Status
|
||||
Location string
|
||||
Protocol Protocol
|
||||
URL string
|
||||
}
|
||||
|
||||
|
||||
+7
-41
@@ -69,6 +69,7 @@ func NewH2muxConnection(
|
||||
connIndex uint8,
|
||||
observer *Observer,
|
||||
gracefulShutdownC <-chan struct{},
|
||||
log *zerolog.Logger,
|
||||
) (*h2muxConnection, error, bool) {
|
||||
h := &h2muxConnection{
|
||||
orchestrator: orchestrator,
|
||||
@@ -79,6 +80,7 @@ func NewH2muxConnection(
|
||||
observer: observer,
|
||||
gracefulShutdownC: gracefulShutdownC,
|
||||
newRPCClientFunc: newRegistrationRPCClient,
|
||||
log: log,
|
||||
}
|
||||
|
||||
// Establish a muxed connection with the edge
|
||||
@@ -121,46 +123,6 @@ func (h *h2muxConnection) ServeNamedTunnel(ctx context.Context, namedTunnel *Nam
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *h2muxConnection) ServeClassicTunnel(ctx context.Context, classicTunnel *ClassicTunnelProperties, credentialManager CredentialManager, registrationOptions *tunnelpogs.RegistrationOptions, connectedFuse ConnectedFuse) error {
|
||||
errGroup, serveCtx := errgroup.WithContext(ctx)
|
||||
errGroup.Go(func() error {
|
||||
return h.serveMuxer(serveCtx)
|
||||
})
|
||||
|
||||
errGroup.Go(func() (err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
connectedFuse.Connected()
|
||||
}
|
||||
}()
|
||||
if classicTunnel.UseReconnectToken && connectedFuse.IsConnected() {
|
||||
err := h.reconnectTunnel(ctx, credentialManager, classicTunnel, registrationOptions)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// log errors and proceed to RegisterTunnel
|
||||
h.observer.log.Err(err).
|
||||
Uint8(LogFieldConnIndex, h.connIndex).
|
||||
Msg("Couldn't reconnect connection. Re-registering it instead.")
|
||||
}
|
||||
return h.registerTunnel(ctx, credentialManager, classicTunnel, registrationOptions)
|
||||
})
|
||||
|
||||
errGroup.Go(func() error {
|
||||
h.controlLoop(serveCtx, connectedFuse, false)
|
||||
return nil
|
||||
})
|
||||
|
||||
err := errGroup.Wait()
|
||||
if err == errMuxerStopped {
|
||||
if h.stoppedGracefully {
|
||||
return nil
|
||||
}
|
||||
h.observer.log.Info().Uint8(LogFieldConnIndex, h.connIndex).Msg("Unexpected muxer shutdown")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *h2muxConnection) serveMuxer(ctx context.Context) error {
|
||||
// All routines should stop when muxer finish serving. When muxer is shutdown
|
||||
// gracefully, it doesn't return an error, so we need to return errMuxerShutdown
|
||||
@@ -234,7 +196,7 @@ func (h *h2muxConnection) ServeStream(stream *h2mux.MuxedStream) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedRequest(req), sourceConnectionType == TypeWebsocket)
|
||||
err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedHTTPRequest(req, h.log), sourceConnectionType == TypeWebsocket)
|
||||
if err != nil {
|
||||
respWriter.WriteErrorResponse()
|
||||
}
|
||||
@@ -257,6 +219,10 @@ type h2muxRespWriter struct {
|
||||
*h2mux.MuxedStream
|
||||
}
|
||||
|
||||
func (rp *h2muxRespWriter) AddTrailer(trailerName, trailerValue string) {
|
||||
// do nothing. we don't support trailers over h2mux
|
||||
}
|
||||
|
||||
func (rp *h2muxRespWriter) WriteRespHeaders(status int, header http.Header) error {
|
||||
headers := H1ResponseToH2ResponseHeaders(status, header)
|
||||
headers = append(headers, h2mux.Header{Name: ResponseMetaHeader, Value: responseMetaHeaderOrigin})
|
||||
|
||||
@@ -47,8 +47,8 @@ func newH2MuxConnection(t require.TestingT) (*h2muxConnection, *h2mux.Muxer) {
|
||||
edgeMuxChan <- edgeMux
|
||||
}()
|
||||
var connIndex = uint8(0)
|
||||
testObserver := NewObserver(&log, &log, false)
|
||||
h2muxConn, err, _ := NewH2muxConnection(testOrchestrator, testGracePeriod, testMuxerConfig, originConn, connIndex, testObserver, nil)
|
||||
testObserver := NewObserver(&log, &log)
|
||||
h2muxConn, err, _ := NewH2muxConnection(testOrchestrator, testGracePeriod, testMuxerConfig, originConn, connIndex, testObserver, nil, &log)
|
||||
require.NoError(t, err)
|
||||
return h2muxConn, <-edgeMuxChan
|
||||
}
|
||||
|
||||
+60
-33
@@ -115,51 +115,52 @@ func (c *HTTP2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var requestErr error
|
||||
switch connType {
|
||||
case TypeControlStream:
|
||||
if err := c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions); err != nil {
|
||||
c.controlStreamErr = err
|
||||
c.log.Error().Err(err)
|
||||
respWriter.WriteErrorResponse()
|
||||
requestErr = c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions, c.orchestrator)
|
||||
if requestErr != nil {
|
||||
c.controlStreamErr = requestErr
|
||||
}
|
||||
|
||||
case TypeConfiguration:
|
||||
if err := c.handleConfigurationUpdate(respWriter, r); err != nil {
|
||||
c.log.Error().Err(err)
|
||||
respWriter.WriteErrorResponse()
|
||||
}
|
||||
requestErr = c.handleConfigurationUpdate(respWriter, r)
|
||||
|
||||
case TypeWebsocket, TypeHTTP:
|
||||
stripWebsocketUpgradeHeader(r)
|
||||
// Check for tracing on request
|
||||
tr := tracing.NewTracedRequest(r)
|
||||
tr := tracing.NewTracedHTTPRequest(r, c.log)
|
||||
if err := originProxy.ProxyHTTP(respWriter, tr, connType == TypeWebsocket); err != nil {
|
||||
err := fmt.Errorf("Failed to proxy HTTP: %w", err)
|
||||
c.log.Error().Err(err)
|
||||
respWriter.WriteErrorResponse()
|
||||
requestErr = fmt.Errorf("Failed to proxy HTTP: %w", err)
|
||||
}
|
||||
|
||||
case TypeTCP:
|
||||
host, err := getRequestHost(r)
|
||||
if err != nil {
|
||||
err := fmt.Errorf(`cloudflared received a warp-routing request with an empty host value: %w`, err)
|
||||
c.log.Error().Err(err)
|
||||
respWriter.WriteErrorResponse()
|
||||
requestErr = fmt.Errorf(`cloudflared received a warp-routing request with an empty host value: %w`, err)
|
||||
break
|
||||
}
|
||||
|
||||
rws := NewHTTPResponseReadWriterAcker(respWriter, r)
|
||||
if err := originProxy.ProxyTCP(r.Context(), rws, &TCPRequest{
|
||||
Dest: host,
|
||||
CFRay: FindCfRayHeader(r),
|
||||
LBProbe: IsLBProbeRequest(r),
|
||||
}); err != nil {
|
||||
respWriter.WriteErrorResponse()
|
||||
}
|
||||
requestErr = originProxy.ProxyTCP(r.Context(), rws, &TCPRequest{
|
||||
Dest: host,
|
||||
CFRay: FindCfRayHeader(r),
|
||||
LBProbe: IsLBProbeRequest(r),
|
||||
CfTraceID: r.Header.Get(tracing.TracerContextName),
|
||||
})
|
||||
|
||||
default:
|
||||
err := fmt.Errorf("Received unknown connection type: %s", connType)
|
||||
c.log.Error().Err(err)
|
||||
respWriter.WriteErrorResponse()
|
||||
requestErr = fmt.Errorf("Received unknown connection type: %s", connType)
|
||||
}
|
||||
|
||||
if requestErr != nil {
|
||||
c.log.Error().Err(requestErr).Msg("failed to serve incoming request")
|
||||
|
||||
// WriteErrorResponse will return false if status was already written. we need to abort handler.
|
||||
if !respWriter.WriteErrorResponse() {
|
||||
c.log.Debug().Msg("Handler aborted due to failure to write error response after status already sent")
|
||||
panic(http.ErrAbortHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,11 +191,12 @@ func (c *HTTP2Connection) close() {
|
||||
}
|
||||
|
||||
type http2RespWriter struct {
|
||||
r io.Reader
|
||||
w http.ResponseWriter
|
||||
flusher http.Flusher
|
||||
shouldFlush bool
|
||||
log *zerolog.Logger
|
||||
r io.Reader
|
||||
w http.ResponseWriter
|
||||
flusher http.Flusher
|
||||
shouldFlush bool
|
||||
statusWritten bool
|
||||
log *zerolog.Logger
|
||||
}
|
||||
|
||||
func NewHTTP2RespWriter(r *http.Request, w http.ResponseWriter, connType Type, log *zerolog.Logger) (*http2RespWriter, error) {
|
||||
@@ -218,11 +220,20 @@ func NewHTTP2RespWriter(r *http.Request, w http.ResponseWriter, connType Type, l
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rp *http2RespWriter) AddTrailer(trailerName, trailerValue string) {
|
||||
if !rp.statusWritten {
|
||||
rp.log.Warn().Msg("Tried to add Trailer to response before status written. Ignoring...")
|
||||
return
|
||||
}
|
||||
|
||||
rp.w.Header().Add(http2.TrailerPrefix+trailerName, trailerValue)
|
||||
}
|
||||
|
||||
func (rp *http2RespWriter) WriteRespHeaders(status int, header http.Header) error {
|
||||
dest := rp.w.Header()
|
||||
userHeaders := make(http.Header, len(header))
|
||||
for name, values := range header {
|
||||
// Since these are http2 headers, they're required to be lowercase
|
||||
// lowercase headers for simplicity check
|
||||
h2name := strings.ToLower(name)
|
||||
|
||||
if h2name == "content-length" {
|
||||
@@ -231,6 +242,12 @@ func (rp *http2RespWriter) WriteRespHeaders(status int, header http.Header) erro
|
||||
dest[name] = values
|
||||
}
|
||||
|
||||
if h2name == tracing.IntCloudflaredTracingHeader {
|
||||
// Add cf-int-cloudflared-tracing header outside of serialized userHeaders
|
||||
dest[tracing.CanonicalCloudflaredTracingHeader] = values
|
||||
continue
|
||||
}
|
||||
|
||||
if !IsControlResponseHeader(h2name) || IsWebsocketClientHeader(h2name) {
|
||||
// User headers, on the other hand, must all be serialized so that
|
||||
// HTTP/2 header validation won't be applied to HTTP/1 header values
|
||||
@@ -240,24 +257,34 @@ func (rp *http2RespWriter) WriteRespHeaders(status int, header http.Header) erro
|
||||
|
||||
// Perform user header serialization and set them in the single header
|
||||
dest.Set(CanonicalResponseUserHeaders, SerializeHeaders(userHeaders))
|
||||
|
||||
rp.setResponseMetaHeader(responseMetaHeaderOrigin)
|
||||
// HTTP2 removes support for 101 Switching Protocols https://tools.ietf.org/html/rfc7540#section-8.1.1
|
||||
if status == http.StatusSwitchingProtocols {
|
||||
status = http.StatusOK
|
||||
}
|
||||
rp.w.WriteHeader(status)
|
||||
if IsServerSentEvent(header) {
|
||||
if shouldFlush(header) {
|
||||
rp.shouldFlush = true
|
||||
}
|
||||
if rp.shouldFlush {
|
||||
rp.flusher.Flush()
|
||||
}
|
||||
|
||||
rp.statusWritten = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rp *http2RespWriter) WriteErrorResponse() {
|
||||
func (rp *http2RespWriter) WriteErrorResponse() bool {
|
||||
if rp.statusWritten {
|
||||
return false
|
||||
}
|
||||
|
||||
rp.setResponseMetaHeader(responseMetaHeaderCfd)
|
||||
rp.w.WriteHeader(http.StatusBadGateway)
|
||||
rp.statusWritten = true
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (rp *http2RespWriter) setResponseMetaHeader(value string) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -32,7 +33,7 @@ func newTestHTTP2Connection() (*HTTP2Connection, net.Conn) {
|
||||
edgeConn, cfdConn := net.Pipe()
|
||||
var connIndex = uint8(0)
|
||||
log := zerolog.Nop()
|
||||
obs := NewObserver(&log, &log, false)
|
||||
obs := NewObserver(&log, &log)
|
||||
controlStream := NewControlStream(
|
||||
obs,
|
||||
mockConnectedFuse{},
|
||||
@@ -40,7 +41,9 @@ func newTestHTTP2Connection() (*HTTP2Connection, net.Conn) {
|
||||
connIndex,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
1*time.Second,
|
||||
HTTP2,
|
||||
)
|
||||
return NewHTTP2Connection(
|
||||
cfdConn,
|
||||
@@ -166,18 +169,27 @@ type mockNamedTunnelRPCClient struct {
|
||||
unregistered chan struct{}
|
||||
}
|
||||
|
||||
func (mc mockNamedTunnelRPCClient) SendLocalConfiguration(c context.Context, config []byte, observer *Observer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mc mockNamedTunnelRPCClient) RegisterConnection(
|
||||
c context.Context,
|
||||
properties *NamedTunnelProperties,
|
||||
options *tunnelpogs.ConnectionOptions,
|
||||
connIndex uint8,
|
||||
edgeAddress net.IP,
|
||||
observer *Observer,
|
||||
) error {
|
||||
) (*tunnelpogs.ConnectionDetails, error) {
|
||||
if mc.shouldFail != nil {
|
||||
return mc.shouldFail
|
||||
return nil, mc.shouldFail
|
||||
}
|
||||
close(mc.registered)
|
||||
return nil
|
||||
return &tunnelpogs.ConnectionDetails{
|
||||
Location: "LIS",
|
||||
UUID: uuid.New(),
|
||||
TunnelIsRemotelyManaged: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (mc mockNamedTunnelRPCClient) GracefulShutdown(ctx context.Context, gracePeriod time.Duration) {
|
||||
@@ -345,15 +357,17 @@ func TestServeControlStream(t *testing.T) {
|
||||
unregistered: make(chan struct{}),
|
||||
}
|
||||
|
||||
obs := NewObserver(&log, &log, false)
|
||||
obs := NewObserver(&log, &log)
|
||||
controlStream := NewControlStream(
|
||||
obs,
|
||||
mockConnectedFuse{},
|
||||
&NamedTunnelProperties{},
|
||||
1,
|
||||
nil,
|
||||
rpcClientFactory.newMockRPCClient,
|
||||
nil,
|
||||
1*time.Second,
|
||||
HTTP2,
|
||||
)
|
||||
http2Conn.controlStreamHandler = controlStream
|
||||
|
||||
@@ -395,15 +409,17 @@ func TestFailRegistration(t *testing.T) {
|
||||
unregistered: make(chan struct{}),
|
||||
}
|
||||
|
||||
obs := NewObserver(&log, &log, false)
|
||||
obs := NewObserver(&log, &log)
|
||||
controlStream := NewControlStream(
|
||||
obs,
|
||||
mockConnectedFuse{},
|
||||
&NamedTunnelProperties{},
|
||||
http2Conn.connIndex,
|
||||
nil,
|
||||
rpcClientFactory.newMockRPCClient,
|
||||
nil,
|
||||
1*time.Second,
|
||||
HTTP2,
|
||||
)
|
||||
http2Conn.controlStreamHandler = controlStream
|
||||
|
||||
@@ -440,16 +456,18 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
|
||||
events := &eventCollectorSink{}
|
||||
|
||||
shutdownC := make(chan struct{})
|
||||
obs := NewObserver(&log, &log, false)
|
||||
obs := NewObserver(&log, &log)
|
||||
obs.RegisterSink(events)
|
||||
controlStream := NewControlStream(
|
||||
obs,
|
||||
mockConnectedFuse{},
|
||||
&NamedTunnelProperties{},
|
||||
http2Conn.connIndex,
|
||||
nil,
|
||||
rpcClientFactory.newMockRPCClient,
|
||||
shutdownC,
|
||||
1*time.Second,
|
||||
HTTP2,
|
||||
)
|
||||
|
||||
http2Conn.controlStreamHandler = controlStream
|
||||
@@ -477,7 +495,7 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
|
||||
|
||||
select {
|
||||
case <-rpcClientFactory.registered:
|
||||
break //ok
|
||||
break // ok
|
||||
case <-time.Tick(time.Second):
|
||||
t.Fatal("timeout out waiting for registration")
|
||||
}
|
||||
@@ -487,7 +505,7 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
|
||||
|
||||
select {
|
||||
case <-rpcClientFactory.unregistered:
|
||||
break //ok
|
||||
break // ok
|
||||
case <-time.Tick(time.Second):
|
||||
t.Fatal("timeout out waiting for unregistered signal")
|
||||
}
|
||||
|
||||
+41
-1
@@ -13,6 +13,7 @@ const (
|
||||
MetricsNamespace = "cloudflared"
|
||||
TunnelSubsystem = "tunnel"
|
||||
muxerSubsystem = "muxer"
|
||||
configSubsystem = "config"
|
||||
)
|
||||
|
||||
type muxerMetrics struct {
|
||||
@@ -36,6 +37,11 @@ type muxerMetrics struct {
|
||||
compRateAve *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
type localConfigMetrics struct {
|
||||
pushes prometheus.Counter
|
||||
pushesErrors prometheus.Counter
|
||||
}
|
||||
|
||||
type tunnelMetrics struct {
|
||||
timerRetries prometheus.Gauge
|
||||
serverLocations *prometheus.GaugeVec
|
||||
@@ -51,6 +57,39 @@ type tunnelMetrics struct {
|
||||
muxerMetrics *muxerMetrics
|
||||
tunnelsHA tunnelsForHA
|
||||
userHostnamesCounts *prometheus.CounterVec
|
||||
|
||||
localConfigMetrics *localConfigMetrics
|
||||
}
|
||||
|
||||
func newLocalConfigMetrics() *localConfigMetrics {
|
||||
|
||||
pushesMetric := prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: MetricsNamespace,
|
||||
Subsystem: configSubsystem,
|
||||
Name: "local_config_pushes",
|
||||
Help: "Number of local configuration pushes to the edge",
|
||||
},
|
||||
)
|
||||
|
||||
pushesErrorsMetric := prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: MetricsNamespace,
|
||||
Subsystem: configSubsystem,
|
||||
Name: "local_config_pushes_errors",
|
||||
Help: "Number of errors occurred during local configuration pushes",
|
||||
},
|
||||
)
|
||||
|
||||
prometheus.MustRegister(
|
||||
pushesMetric,
|
||||
pushesErrorsMetric,
|
||||
)
|
||||
|
||||
return &localConfigMetrics{
|
||||
pushes: pushesMetric,
|
||||
pushesErrors: pushesErrorsMetric,
|
||||
}
|
||||
}
|
||||
|
||||
func newMuxerMetrics() *muxerMetrics {
|
||||
@@ -328,7 +367,7 @@ func initTunnelMetrics() *tunnelMetrics {
|
||||
Name: "server_locations",
|
||||
Help: "Where each tunnel is connected to. 1 means current location, 0 means previous locations.",
|
||||
},
|
||||
[]string{"connection_id", "location"},
|
||||
[]string{"connection_id", "edge_location"},
|
||||
)
|
||||
prometheus.MustRegister(serverLocations)
|
||||
|
||||
@@ -386,6 +425,7 @@ func initTunnelMetrics() *tunnelMetrics {
|
||||
regFail: registerFail,
|
||||
rpcFail: rpcFail,
|
||||
userHostnamesCounts: userHostnamesCounts,
|
||||
localConfigMetrics: newLocalConfigMetrics(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package connection
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
|
||||
const (
|
||||
LogFieldLocation = "location"
|
||||
LogFieldIPAddress = "ip"
|
||||
observerChannelBufferSize = 16
|
||||
)
|
||||
|
||||
@@ -16,7 +18,6 @@ type Observer struct {
|
||||
logTransport *zerolog.Logger
|
||||
metrics *tunnelMetrics
|
||||
tunnelEventChan chan Event
|
||||
uiEnabled bool
|
||||
addSinkChan chan EventSink
|
||||
}
|
||||
|
||||
@@ -24,12 +25,11 @@ type EventSink interface {
|
||||
OnTunnelEvent(event Event)
|
||||
}
|
||||
|
||||
func NewObserver(log, logTransport *zerolog.Logger, uiEnabled bool) *Observer {
|
||||
func NewObserver(log, logTransport *zerolog.Logger) *Observer {
|
||||
o := &Observer{
|
||||
log: log,
|
||||
logTransport: logTransport,
|
||||
metrics: newTunnelMetrics(),
|
||||
uiEnabled: uiEnabled,
|
||||
tunnelEventChan: make(chan Event, observerChannelBufferSize),
|
||||
addSinkChan: make(chan EventSink, observerChannelBufferSize),
|
||||
}
|
||||
@@ -41,11 +41,12 @@ func (o *Observer) RegisterSink(sink EventSink) {
|
||||
o.addSinkChan <- sink
|
||||
}
|
||||
|
||||
func (o *Observer) logServerInfo(connIndex uint8, location, msg string) {
|
||||
func (o *Observer) logServerInfo(connIndex uint8, location string, address net.IP, msg string) {
|
||||
o.sendEvent(Event{Index: connIndex, EventType: Connected, Location: location})
|
||||
o.log.Info().
|
||||
Uint8(LogFieldConnIndex, connIndex).
|
||||
Str(LogFieldLocation, location).
|
||||
IPAddr(LogFieldIPAddress, address).
|
||||
Msg(msg)
|
||||
o.metrics.registerServerLocation(uint8ToString(connIndex), location)
|
||||
}
|
||||
@@ -54,8 +55,8 @@ func (o *Observer) sendRegisteringEvent(connIndex uint8) {
|
||||
o.sendEvent(Event{Index: connIndex, EventType: RegisteringTunnel})
|
||||
}
|
||||
|
||||
func (o *Observer) sendConnectedEvent(connIndex uint8, location string) {
|
||||
o.sendEvent(Event{Index: connIndex, EventType: Connected, Location: location})
|
||||
func (o *Observer) sendConnectedEvent(connIndex uint8, protocol Protocol, location string) {
|
||||
o.sendEvent(Event{Index: connIndex, EventType: Connected, Protocol: protocol, Location: location})
|
||||
}
|
||||
|
||||
func (o *Observer) SendURL(url string) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func TestSendUrl(t *testing.T) {
|
||||
observer := NewObserver(&log, &log, false)
|
||||
observer := NewObserver(&log, &log)
|
||||
|
||||
observer.SendURL("my-url.com")
|
||||
assert.Equal(t, 1.0, getCounterValue(t, observer.metrics.userHostnamesCounts, "https://my-url.com"))
|
||||
@@ -63,7 +63,7 @@ func TestRegisterServerLocation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestObserverEventsDontBlock(t *testing.T) {
|
||||
observer := NewObserver(&log, &log, false)
|
||||
observer := NewObserver(&log, &log)
|
||||
var mu sync.Mutex
|
||||
observer.RegisterSink(EventSinkFunc(func(_ Event) {
|
||||
// callback will block if lock is already held
|
||||
|
||||
+23
-6
@@ -1,6 +1,7 @@
|
||||
package connection
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"sync"
|
||||
@@ -130,6 +131,7 @@ type autoProtocolSelector struct {
|
||||
refreshAfter time.Time
|
||||
ttl time.Duration
|
||||
log *zerolog.Logger
|
||||
needPQ bool
|
||||
}
|
||||
|
||||
func newAutoProtocolSelector(
|
||||
@@ -139,6 +141,7 @@ func newAutoProtocolSelector(
|
||||
fetchFunc PercentageFetcher,
|
||||
ttl time.Duration,
|
||||
log *zerolog.Logger,
|
||||
needPQ bool,
|
||||
) *autoProtocolSelector {
|
||||
return &autoProtocolSelector{
|
||||
current: current,
|
||||
@@ -148,6 +151,7 @@ func newAutoProtocolSelector(
|
||||
refreshAfter: time.Now().Add(ttl),
|
||||
ttl: ttl,
|
||||
log: log,
|
||||
needPQ: needPQ,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +191,9 @@ func getProtocol(protocolPool []Protocol, fetchFunc PercentageFetcher, switchThr
|
||||
func (s *autoProtocolSelector) Fallback() (Protocol, bool) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
if s.needPQ {
|
||||
return 0, false
|
||||
}
|
||||
return s.current.fallback()
|
||||
}
|
||||
|
||||
@@ -199,9 +206,14 @@ func NewProtocolSelector(
|
||||
fetchFunc PercentageFetcher,
|
||||
ttl time.Duration,
|
||||
log *zerolog.Logger,
|
||||
needPQ bool,
|
||||
) (ProtocolSelector, error) {
|
||||
// Classic tunnel is only supported with h2mux
|
||||
if namedTunnel == nil {
|
||||
if needPQ {
|
||||
return nil, errors.New("Classic tunnel does not support post-quantum")
|
||||
}
|
||||
|
||||
return &staticProtocolSelector{
|
||||
current: H2mux,
|
||||
}, nil
|
||||
@@ -209,8 +221,11 @@ func NewProtocolSelector(
|
||||
|
||||
threshold := switchThreshold(namedTunnel.Credentials.AccountTag)
|
||||
fetchedProtocol, err := getProtocol([]Protocol{QUIC, HTTP2}, fetchFunc, threshold)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Unable to lookup protocol. Defaulting to `http2`. If this fails, you can set `--protocol h2mux` in your cloudflared command.")
|
||||
if err != nil && protocolFlag == "auto" {
|
||||
log.Err(err).Msg("Unable to lookup protocol. Defaulting to `http2`. If this fails, you can attempt `--protocol quic` instead.")
|
||||
if needPQ {
|
||||
return nil, errors.New("http2 does not support post-quantum")
|
||||
}
|
||||
return &staticProtocolSelector{
|
||||
current: HTTP2,
|
||||
}, nil
|
||||
@@ -221,10 +236,10 @@ func NewProtocolSelector(
|
||||
protocolFlag = HTTP2.String()
|
||||
fetchedProtocol = HTTP2Warp
|
||||
}
|
||||
return selectWarpRoutingProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol)
|
||||
return selectWarpRoutingProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol, needPQ)
|
||||
}
|
||||
|
||||
return selectNamedTunnelProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol)
|
||||
return selectNamedTunnelProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol, needPQ)
|
||||
}
|
||||
|
||||
func selectNamedTunnelProtocols(
|
||||
@@ -234,6 +249,7 @@ func selectNamedTunnelProtocols(
|
||||
log *zerolog.Logger,
|
||||
threshold int32,
|
||||
protocol Protocol,
|
||||
needPQ bool,
|
||||
) (ProtocolSelector, error) {
|
||||
// If the user picks a protocol, then we stick to it no matter what.
|
||||
switch protocolFlag {
|
||||
@@ -248,7 +264,7 @@ func selectNamedTunnelProtocols(
|
||||
// If the user does not pick (hopefully the majority) then we use the one derived from the TXT DNS record and
|
||||
// fallback on failures.
|
||||
if protocolFlag == AutoSelectFlag {
|
||||
return newAutoProtocolSelector(protocol, []Protocol{QUIC, HTTP2, H2mux}, threshold, fetchFunc, ttl, log), nil
|
||||
return newAutoProtocolSelector(protocol, []Protocol{QUIC, HTTP2, H2mux}, threshold, fetchFunc, ttl, log, needPQ), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Unknown protocol %s, %s", protocolFlag, AvailableProtocolFlagMessage)
|
||||
@@ -261,6 +277,7 @@ func selectWarpRoutingProtocols(
|
||||
log *zerolog.Logger,
|
||||
threshold int32,
|
||||
protocol Protocol,
|
||||
needPQ bool,
|
||||
) (ProtocolSelector, error) {
|
||||
// If the user picks a protocol, then we stick to it no matter what.
|
||||
switch protocolFlag {
|
||||
@@ -273,7 +290,7 @@ func selectWarpRoutingProtocols(
|
||||
// If the user does not pick (hopefully the majority) then we use the one derived from the TXT DNS record and
|
||||
// fallback on failures.
|
||||
if protocolFlag == AutoSelectFlag {
|
||||
return newAutoProtocolSelector(protocol, []Protocol{QUICWarp, HTTP2Warp}, threshold, fetchFunc, ttl, log), nil
|
||||
return newAutoProtocolSelector(protocol, []Protocol{QUICWarp, HTTP2Warp}, threshold, fetchFunc, ttl, log, needPQ), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Unknown protocol %s, %s", protocolFlag, AvailableProtocolFlagMessage)
|
||||
|
||||
@@ -219,7 +219,7 @@ func TestNewProtocolSelector(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
selector, err := NewProtocolSelector(test.protocol, test.warpRoutingEnabled, test.namedTunnelConfig, test.fetchFunc, testNoTTL, &log)
|
||||
selector, err := NewProtocolSelector(test.protocol, test.warpRoutingEnabled, test.namedTunnelConfig, test.fetchFunc, testNoTTL, &log, false)
|
||||
if test.wantErr {
|
||||
assert.Error(t, err, fmt.Sprintf("test %s failed", test.name))
|
||||
} else {
|
||||
@@ -237,7 +237,7 @@ func TestNewProtocolSelector(t *testing.T) {
|
||||
|
||||
func TestAutoProtocolSelectorRefresh(t *testing.T) {
|
||||
fetcher := dynamicMockFetcher{}
|
||||
selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log)
|
||||
selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, H2mux, selector.Current())
|
||||
|
||||
@@ -267,7 +267,7 @@ func TestAutoProtocolSelectorRefresh(t *testing.T) {
|
||||
func TestHTTP2ProtocolSelectorRefresh(t *testing.T) {
|
||||
fetcher := dynamicMockFetcher{}
|
||||
// Since the user chooses http2 on purpose, we always stick to it.
|
||||
selector, err := NewProtocolSelector("http2", noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log)
|
||||
selector, err := NewProtocolSelector("http2", noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, HTTP2, selector.Current())
|
||||
|
||||
@@ -297,7 +297,7 @@ func TestHTTP2ProtocolSelectorRefresh(t *testing.T) {
|
||||
func TestProtocolSelectorRefreshTTL(t *testing.T) {
|
||||
fetcher := dynamicMockFetcher{}
|
||||
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}}
|
||||
selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), time.Hour, &log)
|
||||
selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), time.Hour, &log, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, QUIC, selector.Current())
|
||||
|
||||
|
||||
+285
-55
@@ -7,18 +7,24 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/cloudflare/cloudflared/datagramsession"
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
"github.com/cloudflare/cloudflared/packet"
|
||||
quicpogs "github.com/cloudflare/cloudflared/quic"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
@@ -31,14 +37,27 @@ const (
|
||||
HTTPMethodKey = "HttpMethod"
|
||||
// HTTPHostKey is used to get or set http Method in QUIC ALPN if the underlying proxy connection type is HTTP.
|
||||
HTTPHostKey = "HttpHost"
|
||||
|
||||
QUICMetadataFlowID = "FlowID"
|
||||
// emperically this capacity has been working well
|
||||
demuxChanCapacity = 16
|
||||
)
|
||||
|
||||
var (
|
||||
portForConnIndex = make(map[uint8]int, 0)
|
||||
portMapMutex sync.Mutex
|
||||
)
|
||||
|
||||
// QUICConnection represents the type that facilitates Proxying via QUIC streams.
|
||||
type QUICConnection struct {
|
||||
session quic.Session
|
||||
logger *zerolog.Logger
|
||||
orchestrator Orchestrator
|
||||
sessionManager datagramsession.Manager
|
||||
session quic.Connection
|
||||
logger *zerolog.Logger
|
||||
orchestrator Orchestrator
|
||||
// sessionManager tracks active sessions. It receives datagrams from quic connection via datagramMuxer
|
||||
sessionManager datagramsession.Manager
|
||||
// datagramMuxer mux/demux datagrams from quic connection
|
||||
datagramMuxer *quicpogs.DatagramMuxerV2
|
||||
packetRouter *ingress.PacketRouter
|
||||
controlStreamHandler ControlStreamHandler
|
||||
connOptions *tunnelpogs.ConnectionOptions
|
||||
}
|
||||
@@ -47,29 +66,44 @@ type QUICConnection struct {
|
||||
func NewQUICConnection(
|
||||
quicConfig *quic.Config,
|
||||
edgeAddr net.Addr,
|
||||
connIndex uint8,
|
||||
tlsConfig *tls.Config,
|
||||
orchestrator Orchestrator,
|
||||
connOptions *tunnelpogs.ConnectionOptions,
|
||||
controlStreamHandler ControlStreamHandler,
|
||||
logger *zerolog.Logger,
|
||||
packetRouterConfig *ingress.GlobalRouterConfig,
|
||||
) (*QUICConnection, error) {
|
||||
session, err := quic.DialAddr(edgeAddr.String(), tlsConfig, quicConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to dial to edge: %w", err)
|
||||
}
|
||||
|
||||
datagramMuxer, err := quicpogs.NewDatagramMuxer(session)
|
||||
udpConn, err := createUDPConnForConnIndex(connIndex, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sessionManager := datagramsession.NewManager(datagramMuxer, logger)
|
||||
session, err := quic.Dial(udpConn, edgeAddr, edgeAddr.String(), tlsConfig, quicConfig)
|
||||
if err != nil {
|
||||
// close the udp server socket in case of error connecting to the edge
|
||||
udpConn.Close()
|
||||
return nil, &EdgeQuicDialError{Cause: err}
|
||||
}
|
||||
|
||||
// wrap the session, so that the UDPConn is closed after session is closed.
|
||||
session = &wrapCloseableConnQuicConnection{
|
||||
session,
|
||||
udpConn,
|
||||
}
|
||||
|
||||
sessionDemuxChan := make(chan *packet.Session, demuxChanCapacity)
|
||||
datagramMuxer := quicpogs.NewDatagramMuxerV2(session, logger, sessionDemuxChan)
|
||||
sessionManager := datagramsession.NewManager(logger, datagramMuxer.SendToSession, sessionDemuxChan)
|
||||
packetRouter := ingress.NewPacketRouter(packetRouterConfig, datagramMuxer, logger, orchestrator.WarpRoutingEnabled)
|
||||
|
||||
return &QUICConnection{
|
||||
session: session,
|
||||
orchestrator: orchestrator,
|
||||
logger: logger,
|
||||
sessionManager: sessionManager,
|
||||
datagramMuxer: datagramMuxer,
|
||||
packetRouter: packetRouter,
|
||||
controlStreamHandler: controlStreamHandler,
|
||||
connOptions: connOptions,
|
||||
}, nil
|
||||
@@ -105,13 +139,21 @@ func (q *QUICConnection) Serve(ctx context.Context) error {
|
||||
defer cancel()
|
||||
return q.sessionManager.Serve(ctx)
|
||||
})
|
||||
errGroup.Go(func() error {
|
||||
defer cancel()
|
||||
return q.datagramMuxer.ServeReceive(ctx)
|
||||
})
|
||||
errGroup.Go(func() error {
|
||||
defer cancel()
|
||||
return q.packetRouter.Serve(ctx)
|
||||
})
|
||||
|
||||
return errGroup.Wait()
|
||||
}
|
||||
|
||||
func (q *QUICConnection) serveControlStream(ctx context.Context, controlStream quic.Stream) error {
|
||||
// This blocks until the control plane is done.
|
||||
err := q.controlStreamHandler.ServeControlStream(ctx, controlStream, q.connOptions)
|
||||
err := q.controlStreamHandler.ServeControlStream(ctx, controlStream, q.connOptions, q.orchestrator)
|
||||
if err != nil {
|
||||
// Not wrapping error here to be consistent with the http2 message.
|
||||
return err
|
||||
@@ -120,6 +162,11 @@ func (q *QUICConnection) serveControlStream(ctx context.Context, controlStream q
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the session with no errors specified.
|
||||
func (q *QUICConnection) Close() {
|
||||
q.session.CloseWithError(0, "")
|
||||
}
|
||||
|
||||
func (q *QUICConnection) acceptStream(ctx context.Context) error {
|
||||
defer q.Close()
|
||||
for {
|
||||
@@ -131,23 +178,30 @@ func (q *QUICConnection) acceptStream(ctx context.Context) error {
|
||||
}
|
||||
return fmt.Errorf("failed to accept QUIC stream: %w", err)
|
||||
}
|
||||
go func() {
|
||||
stream := quicpogs.NewSafeStreamCloser(quicStream)
|
||||
defer stream.Close()
|
||||
|
||||
if err = q.handleStream(stream); err != nil {
|
||||
q.logger.Err(err).Msg("Failed to handle QUIC stream")
|
||||
}
|
||||
}()
|
||||
go q.runStream(quicStream)
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the session with no errors specified.
|
||||
func (q *QUICConnection) Close() {
|
||||
q.session.CloseWithError(0, "")
|
||||
func (q *QUICConnection) runStream(quicStream quic.Stream) {
|
||||
ctx := quicStream.Context()
|
||||
stream := quicpogs.NewSafeStreamCloser(quicStream)
|
||||
defer stream.Close()
|
||||
|
||||
// we are going to fuse readers/writers from stream <- cloudflared -> origin, and we want to guarantee that
|
||||
// code executed in the code path of handleStream don't trigger an earlier close to the downstream write stream.
|
||||
// So, we wrap the stream with a no-op write closer and only this method can actually close write side of the stream.
|
||||
// A call to close will simulate a close to the read-side, which will fail subsequent reads.
|
||||
noCloseStream := &nopCloserReadWriter{ReadWriteCloser: stream}
|
||||
if err := q.handleStream(ctx, noCloseStream); err != nil {
|
||||
q.logger.Debug().Err(err).Msg("Failed to handle QUIC stream")
|
||||
|
||||
// if we received an error at this level, then close write side of stream with an error, which will result in
|
||||
// RST_STREAM frame.
|
||||
quicStream.CancelWrite(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *QUICConnection) handleStream(stream io.ReadWriteCloser) error {
|
||||
func (q *QUICConnection) handleStream(ctx context.Context, stream io.ReadWriteCloser) error {
|
||||
signature, err := quicpogs.DetermineProtocol(stream)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -156,9 +210,9 @@ func (q *QUICConnection) handleStream(stream io.ReadWriteCloser) error {
|
||||
case quicpogs.DataStreamProtocolSignature:
|
||||
reqServerStream, err := quicpogs.NewRequestServerStream(stream, signature)
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
return q.handleDataStream(reqServerStream)
|
||||
return q.handleDataStream(ctx, reqServerStream)
|
||||
case quicpogs.RPCStreamProtocolSignature:
|
||||
rpcStream, err := quicpogs.NewRPCServerStream(stream, signature)
|
||||
if err != nil {
|
||||
@@ -170,55 +224,105 @@ func (q *QUICConnection) handleStream(stream io.ReadWriteCloser) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (q *QUICConnection) handleDataStream(stream *quicpogs.RequestServerStream) error {
|
||||
connectRequest, err := stream.ReadConnectRequestData()
|
||||
func (q *QUICConnection) handleDataStream(ctx context.Context, stream *quicpogs.RequestServerStream) error {
|
||||
request, err := stream.ReadConnectRequestData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
originProxy, err := q.orchestrator.GetOriginProxy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch connectRequest.Type {
|
||||
case quicpogs.ConnectionTypeHTTP, quicpogs.ConnectionTypeWebsocket:
|
||||
tracedReq, err := buildHTTPRequest(connectRequest, stream)
|
||||
if err != nil {
|
||||
if err, connectResponseSent := q.dispatchRequest(ctx, stream, err, request); err != nil {
|
||||
q.logger.Err(err).Str("type", request.Type.String()).Str("dest", request.Dest).Msg("Request failed")
|
||||
|
||||
// if the connectResponse was already sent and we had an error, we need to propagate it up, so that the stream is
|
||||
// closed with an RST_STREAM frame
|
||||
if connectResponseSent {
|
||||
return err
|
||||
}
|
||||
|
||||
w := newHTTPResponseAdapter(stream)
|
||||
return originProxy.ProxyHTTP(w, tracedReq, connectRequest.Type == quicpogs.ConnectionTypeWebsocket)
|
||||
case quicpogs.ConnectionTypeTCP:
|
||||
rwa := &streamReadWriteAcker{stream}
|
||||
return originProxy.ProxyTCP(context.Background(), rwa, &TCPRequest{Dest: connectRequest.Dest})
|
||||
if writeRespErr := stream.WriteConnectResponseData(err); writeRespErr != nil {
|
||||
return writeRespErr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dispatchRequest will dispatch the request depending on the type and returns an error if it occurs.
|
||||
// More importantly, it also tells if the during processing of the request the ConnectResponse metadata was sent downstream.
|
||||
// This is important since it informs
|
||||
func (q *QUICConnection) dispatchRequest(ctx context.Context, stream *quicpogs.RequestServerStream, err error, request *quicpogs.ConnectRequest) (error, bool) {
|
||||
originProxy, err := q.orchestrator.GetOriginProxy()
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
|
||||
switch request.Type {
|
||||
case quicpogs.ConnectionTypeHTTP, quicpogs.ConnectionTypeWebsocket:
|
||||
tracedReq, err := buildHTTPRequest(ctx, request, stream, q.logger)
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
w := newHTTPResponseAdapter(stream)
|
||||
return originProxy.ProxyHTTP(&w, tracedReq, request.Type == quicpogs.ConnectionTypeWebsocket), w.connectResponseSent
|
||||
|
||||
case quicpogs.ConnectionTypeTCP:
|
||||
rwa := &streamReadWriteAcker{RequestServerStream: stream}
|
||||
metadata := request.MetadataMap()
|
||||
return originProxy.ProxyTCP(ctx, rwa, &TCPRequest{
|
||||
Dest: request.Dest,
|
||||
FlowID: metadata[QUICMetadataFlowID],
|
||||
CfTraceID: metadata[tracing.TracerContextName],
|
||||
}), rwa.connectResponseSent
|
||||
default:
|
||||
return errors.Errorf("unsupported error type: %s", request.Type), false
|
||||
}
|
||||
}
|
||||
|
||||
func (q *QUICConnection) handleRPCStream(rpcStream *quicpogs.RPCServerStream) error {
|
||||
return rpcStream.Serve(q, q, q.logger)
|
||||
if err := rpcStream.Serve(q, q, q.logger); err != nil {
|
||||
q.logger.Err(err).Msg("failed handling RPC stream")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterUdpSession is the RPC method invoked by edge to register and run a session
|
||||
func (q *QUICConnection) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration) error {
|
||||
func (q *QUICConnection) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration, traceContext string) (*tunnelpogs.RegisterUdpSessionResponse, error) {
|
||||
traceCtx := tracing.NewTracedContext(ctx, traceContext, q.logger)
|
||||
ctx, registerSpan := traceCtx.Tracer().Start(traceCtx, "register-session", trace.WithAttributes(
|
||||
attribute.String("session-id", sessionID.String()),
|
||||
attribute.String("dst", fmt.Sprintf("%s:%d", dstIP, dstPort)),
|
||||
))
|
||||
// Each session is a series of datagram from an eyeball to a dstIP:dstPort.
|
||||
// (src port, dst IP, dst port) uniquely identifies a session, so it needs a dedicated connected socket.
|
||||
originProxy, err := ingress.DialUDP(dstIP, dstPort)
|
||||
if err != nil {
|
||||
q.logger.Err(err).Msgf("Failed to create udp proxy to %s:%d", dstIP, dstPort)
|
||||
return err
|
||||
tracing.EndWithErrorStatus(registerSpan, err)
|
||||
return nil, err
|
||||
}
|
||||
registerSpan.SetAttributes(
|
||||
attribute.Bool("socket-bind-success", true),
|
||||
attribute.String("src", originProxy.LocalAddr().String()),
|
||||
)
|
||||
|
||||
session, err := q.sessionManager.RegisterSession(ctx, sessionID, originProxy)
|
||||
if err != nil {
|
||||
q.logger.Err(err).Str("sessionID", sessionID.String()).Msgf("Failed to register udp session")
|
||||
return err
|
||||
tracing.EndWithErrorStatus(registerSpan, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go q.serveUDPSession(session, closeAfterIdleHint)
|
||||
|
||||
q.logger.Debug().Msgf("Registered session %v, %v, %v", sessionID, dstIP, dstPort)
|
||||
return nil
|
||||
q.logger.Debug().Str("sessionID", sessionID.String()).Str("src", originProxy.LocalAddr().String()).Str("dst", fmt.Sprintf("%s:%d", dstIP, dstPort)).Msgf("Registered session")
|
||||
tracing.End(registerSpan)
|
||||
|
||||
resp := tunnelpogs.RegisterUdpSessionResponse{
|
||||
Spans: traceCtx.GetProtoSpans(),
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (q *QUICConnection) serveUDPSession(session *datagramsession.Session, closeAfterIdleHint time.Duration) {
|
||||
@@ -274,23 +378,34 @@ func (q *QUICConnection) UpdateConfiguration(ctx context.Context, version int32,
|
||||
// the client.
|
||||
type streamReadWriteAcker struct {
|
||||
*quicpogs.RequestServerStream
|
||||
connectResponseSent bool
|
||||
}
|
||||
|
||||
// AckConnection acks response back to the proxy.
|
||||
func (s *streamReadWriteAcker) AckConnection() error {
|
||||
return s.WriteConnectResponseData(nil)
|
||||
func (s *streamReadWriteAcker) AckConnection(tracePropagation string) error {
|
||||
metadata := quicpogs.Metadata{
|
||||
Key: tracing.CanonicalCloudflaredTracingHeader,
|
||||
Val: tracePropagation,
|
||||
}
|
||||
s.connectResponseSent = true
|
||||
return s.WriteConnectResponseData(nil, metadata)
|
||||
}
|
||||
|
||||
// httpResponseAdapter translates responses written by the HTTP Proxy into ones that can be used in QUIC.
|
||||
type httpResponseAdapter struct {
|
||||
*quicpogs.RequestServerStream
|
||||
connectResponseSent bool
|
||||
}
|
||||
|
||||
func newHTTPResponseAdapter(s *quicpogs.RequestServerStream) httpResponseAdapter {
|
||||
return httpResponseAdapter{s}
|
||||
return httpResponseAdapter{RequestServerStream: s}
|
||||
}
|
||||
|
||||
func (hrw httpResponseAdapter) WriteRespHeaders(status int, header http.Header) error {
|
||||
func (hrw *httpResponseAdapter) AddTrailer(trailerName, trailerValue string) {
|
||||
// we do not support trailers over QUIC
|
||||
}
|
||||
|
||||
func (hrw *httpResponseAdapter) WriteRespHeaders(status int, header http.Header) error {
|
||||
metadata := make([]quicpogs.Metadata, 0)
|
||||
metadata = append(metadata, quicpogs.Metadata{Key: "HttpStatus", Val: strconv.Itoa(status)})
|
||||
for k, vv := range header {
|
||||
@@ -299,21 +414,32 @@ func (hrw httpResponseAdapter) WriteRespHeaders(status int, header http.Header)
|
||||
metadata = append(metadata, quicpogs.Metadata{Key: httpHeaderKey, Val: v})
|
||||
}
|
||||
}
|
||||
|
||||
return hrw.WriteConnectResponseData(nil, metadata...)
|
||||
}
|
||||
|
||||
func (hrw httpResponseAdapter) WriteErrorResponse(err error) {
|
||||
func (hrw *httpResponseAdapter) WriteErrorResponse(err error) {
|
||||
hrw.WriteConnectResponseData(err, quicpogs.Metadata{Key: "HttpStatus", Val: strconv.Itoa(http.StatusBadGateway)})
|
||||
}
|
||||
|
||||
func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.ReadCloser) (*tracing.TracedRequest, error) {
|
||||
func (hrw *httpResponseAdapter) WriteConnectResponseData(respErr error, metadata ...quicpogs.Metadata) error {
|
||||
hrw.connectResponseSent = true
|
||||
return hrw.RequestServerStream.WriteConnectResponseData(respErr, metadata...)
|
||||
}
|
||||
|
||||
func buildHTTPRequest(
|
||||
ctx context.Context,
|
||||
connectRequest *quicpogs.ConnectRequest,
|
||||
body io.ReadCloser,
|
||||
log *zerolog.Logger,
|
||||
) (*tracing.TracedHTTPRequest, error) {
|
||||
metadata := connectRequest.MetadataMap()
|
||||
dest := connectRequest.Dest
|
||||
method := metadata[HTTPMethodKey]
|
||||
host := metadata[HTTPHostKey]
|
||||
isWebsocket := connectRequest.Type == quicpogs.ConnectionTypeWebsocket
|
||||
|
||||
req, err := http.NewRequest(method, dest, body)
|
||||
req, err := http.NewRequestWithContext(ctx, method, dest, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -348,7 +474,7 @@ func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.ReadClose
|
||||
stripWebsocketUpgradeHeader(req)
|
||||
|
||||
// Check for tracing on request
|
||||
tracedReq := tracing.NewTracedRequest(req)
|
||||
tracedReq := tracing.NewTracedHTTPRequest(req, log)
|
||||
return tracedReq, err
|
||||
}
|
||||
|
||||
@@ -366,3 +492,107 @@ func isTransferEncodingChunked(req *http.Request) bool {
|
||||
// separated value as well.
|
||||
return strings.Contains(strings.ToLower(transferEncodingVal), "chunked")
|
||||
}
|
||||
|
||||
// A helper struct that guarantees a call to close only affects read side, but not write side.
|
||||
type nopCloserReadWriter struct {
|
||||
io.ReadWriteCloser
|
||||
|
||||
// for use by Read only
|
||||
// we don't need a memory barrier here because there is an implicit assumption that
|
||||
// Read calls can't happen concurrently by different go-routines.
|
||||
sawEOF bool
|
||||
// should be updated and read using atomic primitives.
|
||||
// value is read in Read method and written in Close method, which could be done by different
|
||||
// go-routines.
|
||||
closed uint32
|
||||
}
|
||||
|
||||
func (np *nopCloserReadWriter) Read(p []byte) (n int, err error) {
|
||||
if np.sawEOF {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
if atomic.LoadUint32(&np.closed) > 0 {
|
||||
return 0, fmt.Errorf("closed by handler")
|
||||
}
|
||||
|
||||
n, err = np.ReadWriteCloser.Read(p)
|
||||
if err == io.EOF {
|
||||
np.sawEOF = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (np *nopCloserReadWriter) Close() error {
|
||||
atomic.StoreUint32(&np.closed, 1)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// muxerWrapper wraps DatagramMuxerV2 to satisfy the packet.FunnelUniPipe interface
|
||||
type muxerWrapper struct {
|
||||
muxer *quicpogs.DatagramMuxerV2
|
||||
}
|
||||
|
||||
func (rp *muxerWrapper) SendPacket(dst netip.Addr, pk packet.RawPacket) error {
|
||||
return rp.muxer.SendPacket(quicpogs.RawPacket(pk))
|
||||
}
|
||||
|
||||
func (rp *muxerWrapper) ReceivePacket(ctx context.Context) (packet.RawPacket, error) {
|
||||
pk, err := rp.muxer.ReceivePacket(ctx)
|
||||
if err != nil {
|
||||
return packet.RawPacket{}, err
|
||||
}
|
||||
rawPacket, ok := pk.(quicpogs.RawPacket)
|
||||
if ok {
|
||||
return packet.RawPacket(rawPacket), nil
|
||||
}
|
||||
return packet.RawPacket{}, fmt.Errorf("unexpected packet type %+v", pk)
|
||||
}
|
||||
|
||||
func (rp *muxerWrapper) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createUDPConnForConnIndex(connIndex uint8, logger *zerolog.Logger) (*net.UDPConn, error) {
|
||||
portMapMutex.Lock()
|
||||
defer portMapMutex.Unlock()
|
||||
|
||||
// if port was not set yet, it will be zero, so bind will randomly allocate one.
|
||||
if port, ok := portForConnIndex[connIndex]; ok {
|
||||
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: port})
|
||||
// if there wasn't an error, or if port was 0 (independently of error or not, just return)
|
||||
if err == nil {
|
||||
return udpConn, nil
|
||||
} else {
|
||||
logger.Debug().Err(err).Msgf("Unable to reuse port %d for connIndex %d. Falling back to random allocation.", port, connIndex)
|
||||
}
|
||||
}
|
||||
|
||||
// if we reached here, then there was an error or port as not been allocated it.
|
||||
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
||||
if err == nil {
|
||||
udpAddr, ok := (udpConn.LocalAddr()).(*net.UDPAddr)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to cast to udpConn")
|
||||
}
|
||||
portForConnIndex[connIndex] = udpAddr.Port
|
||||
} else {
|
||||
delete(portForConnIndex, connIndex)
|
||||
}
|
||||
|
||||
return udpConn, err
|
||||
}
|
||||
|
||||
type wrapCloseableConnQuicConnection struct {
|
||||
quic.Connection
|
||||
udpConn *net.UDPConn
|
||||
}
|
||||
|
||||
func (w *wrapCloseableConnQuicConnection) CloseWithError(errorCode quic.ApplicationErrorCode, reason string) error {
|
||||
err := w.Connection.CloseWithError(errorCode, reason)
|
||||
w.udpConn.Close()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
+132
-35
@@ -10,7 +10,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -25,17 +25,21 @@ import (
|
||||
"github.com/cloudflare/cloudflared/datagramsession"
|
||||
quicpogs "github.com/cloudflare/cloudflared/quic"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
var (
|
||||
testTLSServerConfig = quicpogs.GenerateTLSConfig()
|
||||
testQUICConfig = &quic.Config{
|
||||
KeepAlive: true,
|
||||
EnableDatagrams: true,
|
||||
ConnectionIDLength: 16,
|
||||
KeepAlivePeriod: 5 * time.Second,
|
||||
EnableDatagrams: true,
|
||||
}
|
||||
)
|
||||
|
||||
var _ ReadWriteAcker = (*streamReadWriteAcker)(nil)
|
||||
|
||||
// TestQUICServer tests if a quic server accepts and responds to a quic client with the acceptance protocol.
|
||||
// It also serves as a demonstration for communication with the QUIC connection started by a cloudflared.
|
||||
func TestQUICServer(t *testing.T) {
|
||||
@@ -137,24 +141,33 @@ func TestQUICServer(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
for i, test := range tests {
|
||||
test := test // capture range variable
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
quicListener, err := quic.Listen(udpListener, testTLSServerConfig, testQUICConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
serverDone := make(chan struct{})
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
quicServer(
|
||||
t, udpListener, testTLSServerConfig, testQUICConfig,
|
||||
test.dest, test.connectionType, test.metadata, test.message, test.expectedResponse,
|
||||
ctx, t, quicListener, test.dest, test.connectionType, test.metadata, test.message, test.expectedResponse,
|
||||
)
|
||||
close(serverDone)
|
||||
}()
|
||||
|
||||
qc := testQUICConnection(udpListener.LocalAddr(), t)
|
||||
go qc.Serve(ctx)
|
||||
qc := testQUICConnection(udpListener.LocalAddr(), t, uint8(i))
|
||||
|
||||
wg.Wait()
|
||||
connDone := make(chan struct{})
|
||||
go func() {
|
||||
qc.Serve(ctx)
|
||||
close(connDone)
|
||||
}()
|
||||
|
||||
<-serverDone
|
||||
cancel()
|
||||
<-connDone
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -163,7 +176,7 @@ type fakeControlStream struct {
|
||||
ControlStreamHandler
|
||||
}
|
||||
|
||||
func (fakeControlStream) ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions) error {
|
||||
func (fakeControlStream) ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error {
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
@@ -172,23 +185,16 @@ func (fakeControlStream) IsStopped() bool {
|
||||
}
|
||||
|
||||
func quicServer(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
conn net.PacketConn,
|
||||
tlsConf *tls.Config,
|
||||
config *quic.Config,
|
||||
listener quic.Listener,
|
||||
dest string,
|
||||
connectionType quicpogs.ConnectionType,
|
||||
metadata []quicpogs.Metadata,
|
||||
message []byte,
|
||||
expectedResponse []byte,
|
||||
) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
earlyListener, err := quic.Listen(conn, tlsConf, config)
|
||||
require.NoError(t, err)
|
||||
|
||||
session, err := earlyListener.Accept(ctx)
|
||||
session, err := listener.Accept(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
quicStream, err := session.OpenStreamSync(context.Background())
|
||||
@@ -220,7 +226,7 @@ func quicServer(
|
||||
|
||||
type mockOriginProxyWithRequest struct{}
|
||||
|
||||
func (moc *mockOriginProxyWithRequest) ProxyHTTP(w ResponseWriter, tr *tracing.TracedRequest, isWebsocket bool) error {
|
||||
func (moc *mockOriginProxyWithRequest) ProxyHTTP(w ResponseWriter, tr *tracing.TracedHTTPRequest, isWebsocket bool) error {
|
||||
// These are a series of crude tests to ensure the headers and http related data is transferred from
|
||||
// metadata.
|
||||
r := tr.Request
|
||||
@@ -475,9 +481,11 @@ func TestBuildHTTPRequest(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
log := zerolog.Nop()
|
||||
for _, test := range tests {
|
||||
test := test // capture range variable
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req, err := buildHTTPRequest(test.connectRequest, test.body)
|
||||
req, err := buildHTTPRequest(context.Background(), test.connectRequest, test.body, &log)
|
||||
assert.NoError(t, err)
|
||||
test.req = test.req.WithContext(req.Context())
|
||||
assert.Equal(t, test.req, req.Request)
|
||||
@@ -486,7 +494,7 @@ func TestBuildHTTPRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
func (moc *mockOriginProxyWithRequest) ProxyTCP(ctx context.Context, rwa ReadWriteAcker, tcpRequest *TCPRequest) error {
|
||||
rwa.AckConnection()
|
||||
rwa.AckConnection("")
|
||||
io.Copy(rwa, rwa)
|
||||
return nil
|
||||
}
|
||||
@@ -500,9 +508,10 @@ func TestServeUDPSession(t *testing.T) {
|
||||
defer udpListener.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
val := udpListener.LocalAddr()
|
||||
|
||||
// Establish QUIC connection with edge
|
||||
edgeQUICSessionChan := make(chan quic.Session)
|
||||
edgeQUICSessionChan := make(chan quic.Connection)
|
||||
go func() {
|
||||
earlyListener, err := quic.Listen(udpListener, testTLSServerConfig, testQUICConfig)
|
||||
require.NoError(t, err)
|
||||
@@ -512,7 +521,8 @@ func TestServeUDPSession(t *testing.T) {
|
||||
edgeQUICSessionChan <- edgeQUICSession
|
||||
}()
|
||||
|
||||
qc := testQUICConnection(udpListener.LocalAddr(), t)
|
||||
// Random index to avoid reusing port
|
||||
qc := testQUICConnection(val, t, 28)
|
||||
go qc.Serve(ctx)
|
||||
|
||||
edgeQUICSession := <-edgeQUICSessionChan
|
||||
@@ -522,7 +532,76 @@ func TestServeUDPSession(t *testing.T) {
|
||||
cancel()
|
||||
}
|
||||
|
||||
func serveSession(ctx context.Context, qc *QUICConnection, edgeQUICSession quic.Session, closeType closeReason, expectedReason string, t *testing.T) {
|
||||
func TestNopCloserReadWriterCloseBeforeEOF(t *testing.T) {
|
||||
readerWriter := nopCloserReadWriter{ReadWriteCloser: &mockReaderNoopWriter{Reader: strings.NewReader("123456789")}}
|
||||
buffer := make([]byte, 5)
|
||||
|
||||
n, err := readerWriter.Read(buffer)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n, 5)
|
||||
|
||||
// close
|
||||
require.NoError(t, readerWriter.Close())
|
||||
|
||||
// read should get error
|
||||
n, err = readerWriter.Read(buffer)
|
||||
require.Equal(t, n, 0)
|
||||
require.Equal(t, err, fmt.Errorf("closed by handler"))
|
||||
}
|
||||
|
||||
func TestNopCloserReadWriterCloseAfterEOF(t *testing.T) {
|
||||
readerWriter := nopCloserReadWriter{ReadWriteCloser: &mockReaderNoopWriter{Reader: strings.NewReader("123456789")}}
|
||||
buffer := make([]byte, 20)
|
||||
|
||||
n, err := readerWriter.Read(buffer)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n, 9)
|
||||
|
||||
// force another read to read eof
|
||||
_, err = readerWriter.Read(buffer)
|
||||
require.Equal(t, err, io.EOF)
|
||||
|
||||
// close
|
||||
require.NoError(t, readerWriter.Close())
|
||||
|
||||
// read should get EOF still
|
||||
n, err = readerWriter.Read(buffer)
|
||||
require.Equal(t, n, 0)
|
||||
require.Equal(t, err, io.EOF)
|
||||
}
|
||||
|
||||
func TestCreateUDPConnReuseSourcePort(t *testing.T) {
|
||||
logger := zerolog.Nop()
|
||||
conn, err := createUDPConnForConnIndex(0, &logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
getPortFunc := func(conn *net.UDPConn) int {
|
||||
addr := conn.LocalAddr().(*net.UDPAddr)
|
||||
return addr.Port
|
||||
}
|
||||
|
||||
initialPort := getPortFunc(conn)
|
||||
|
||||
// close conn
|
||||
conn.Close()
|
||||
|
||||
// should get the same port as before.
|
||||
conn, err = createUDPConnForConnIndex(0, &logger)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, initialPort, getPortFunc(conn))
|
||||
|
||||
// new index, should get a different port
|
||||
conn1, err := createUDPConnForConnIndex(1, &logger)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, initialPort, getPortFunc(conn1))
|
||||
|
||||
// not closing the conn and trying to obtain a new conn for same index should give a different random port
|
||||
conn, err = createUDPConnForConnIndex(0, &logger)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, initialPort, getPortFunc(conn))
|
||||
}
|
||||
|
||||
func serveSession(ctx context.Context, qc *QUICConnection, edgeQUICSession quic.Connection, closeType closeReason, expectedReason string, t *testing.T) {
|
||||
var (
|
||||
payload = []byte(t.Name())
|
||||
)
|
||||
@@ -538,8 +617,12 @@ func serveSession(ctx context.Context, qc *QUICConnection, edgeQUICSession quic.
|
||||
close(sessionDone)
|
||||
}()
|
||||
|
||||
// Send a message to the quic session on edge side, it should be deumx to this datagram session
|
||||
muxedPayload := append(payload, sessionID[:]...)
|
||||
// Send a message to the quic session on edge side, it should be deumx to this datagram v2 session
|
||||
muxedPayload, err := quicpogs.SuffixSessionID(sessionID, payload)
|
||||
require.NoError(t, err)
|
||||
muxedPayload, err = quicpogs.SuffixType(muxedPayload, quicpogs.DatagramTypeUDP)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = edgeQUICSession.SendMessage(muxedPayload)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -583,7 +666,7 @@ const (
|
||||
closedByTimeout
|
||||
)
|
||||
|
||||
func runRPCServer(ctx context.Context, session quic.Session, sessionRPCServer tunnelpogs.SessionManager, configRPCServer tunnelpogs.ConfigurationManager, t *testing.T) {
|
||||
func runRPCServer(ctx context.Context, session quic.Connection, sessionRPCServer tunnelpogs.SessionManager, configRPCServer tunnelpogs.ConfigurationManager, t *testing.T) {
|
||||
stream, err := session.AcceptStream(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -608,8 +691,8 @@ type mockSessionRPCServer struct {
|
||||
calledUnregisterChan chan struct{}
|
||||
}
|
||||
|
||||
func (s mockSessionRPCServer) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfter time.Duration) error {
|
||||
return fmt.Errorf("mockSessionRPCServer doesn't implement RegisterUdpSession")
|
||||
func (s mockSessionRPCServer) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfter time.Duration, traceContext string) (*pogs.RegisterUdpSessionResponse, error) {
|
||||
return nil, fmt.Errorf("mockSessionRPCServer doesn't implement RegisterUdpSession")
|
||||
}
|
||||
|
||||
func (s mockSessionRPCServer) UnregisterUdpSession(ctx context.Context, sessionID uuid.UUID, reason string) error {
|
||||
@@ -623,7 +706,7 @@ func (s mockSessionRPCServer) UnregisterUdpSession(ctx context.Context, sessionI
|
||||
return nil
|
||||
}
|
||||
|
||||
func testQUICConnection(udpListenerAddr net.Addr, t *testing.T) *QUICConnection {
|
||||
func testQUICConnection(udpListenerAddr net.Addr, t *testing.T, index uint8) *QUICConnection {
|
||||
tlsClientConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{"argotunnel"},
|
||||
@@ -633,12 +716,26 @@ func testQUICConnection(udpListenerAddr net.Addr, t *testing.T) *QUICConnection
|
||||
qc, err := NewQUICConnection(
|
||||
testQUICConfig,
|
||||
udpListenerAddr,
|
||||
index,
|
||||
tlsClientConfig,
|
||||
&mockOrchestrator{originProxy: &mockOriginProxyWithRequest{}},
|
||||
&tunnelpogs.ConnectionOptions{},
|
||||
fakeControlStream{},
|
||||
&log,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
return qc
|
||||
}
|
||||
|
||||
type mockReaderNoopWriter struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
func (m *mockReaderNoopWriter) Write(p []byte) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (m *mockReaderNoopWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
+28
-8
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
@@ -57,6 +58,12 @@ type NamedTunnelRPCClient interface {
|
||||
config *NamedTunnelProperties,
|
||||
options *tunnelpogs.ConnectionOptions,
|
||||
connIndex uint8,
|
||||
edgeAddress net.IP,
|
||||
observer *Observer,
|
||||
) (*tunnelpogs.ConnectionDetails, error)
|
||||
SendLocalConfiguration(
|
||||
c context.Context,
|
||||
config []byte,
|
||||
observer *Observer,
|
||||
) error
|
||||
GracefulShutdown(ctx context.Context, gracePeriod time.Duration)
|
||||
@@ -89,8 +96,9 @@ func (rsc *registrationServerClient) RegisterConnection(
|
||||
properties *NamedTunnelProperties,
|
||||
options *tunnelpogs.ConnectionOptions,
|
||||
connIndex uint8,
|
||||
edgeAddress net.IP,
|
||||
observer *Observer,
|
||||
) error {
|
||||
) (*tunnelpogs.ConnectionDetails, error) {
|
||||
conn, err := rsc.client.RegisterConnection(
|
||||
ctx,
|
||||
properties.Credentials.Auth(),
|
||||
@@ -101,18 +109,26 @@ func (rsc *registrationServerClient) RegisterConnection(
|
||||
if err != nil {
|
||||
if err.Error() == DuplicateConnectionError {
|
||||
observer.metrics.regFail.WithLabelValues("dup_edge_conn", "registerConnection").Inc()
|
||||
return errDuplicationConnection
|
||||
return nil, errDuplicationConnection
|
||||
}
|
||||
observer.metrics.regFail.WithLabelValues("server_error", "registerConnection").Inc()
|
||||
return serverRegistrationErrorFromRPC(err)
|
||||
return nil, serverRegistrationErrorFromRPC(err)
|
||||
}
|
||||
|
||||
observer.metrics.regSuccess.WithLabelValues("registerConnection").Inc()
|
||||
|
||||
observer.logServerInfo(connIndex, conn.Location, fmt.Sprintf("Connection %s registered", conn.UUID))
|
||||
observer.sendConnectedEvent(connIndex, conn.Location)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
return nil
|
||||
func (rsc *registrationServerClient) SendLocalConfiguration(ctx context.Context, config []byte, observer *Observer) (err error) {
|
||||
observer.metrics.localConfigMetrics.pushes.Inc()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
observer.metrics.localConfigMetrics.pushesErrors.Inc()
|
||||
}
|
||||
}()
|
||||
|
||||
return rsc.client.SendLocalConfiguration(ctx, config)
|
||||
}
|
||||
|
||||
func (rsc *registrationServerClient) GracefulShutdown(ctx context.Context, gracePeriod time.Duration) {
|
||||
@@ -258,7 +274,7 @@ func (h *h2muxConnection) logServerInfo(ctx context.Context, rpcClient *tunnelSe
|
||||
h.observer.log.Err(err).Msg("Failed to retrieve server information")
|
||||
return err
|
||||
}
|
||||
h.observer.logServerInfo(h.connIndex, serverInfo.LocationName, "Connection established")
|
||||
h.observer.logServerInfo(h.connIndex, serverInfo.LocationName, net.IP{}, "Connection established")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -274,9 +290,13 @@ func (h *h2muxConnection) registerNamedTunnel(
|
||||
rpcClient := h.newRPCClientFunc(ctx, stream, h.observer.log)
|
||||
defer rpcClient.Close()
|
||||
|
||||
if err = rpcClient.RegisterConnection(ctx, namedTunnel, connOptions, h.connIndex, h.observer); err != nil {
|
||||
registrationDetails, err := rpcClient.RegisterConnection(ctx, namedTunnel, connOptions, h.connIndex, nil, h.observer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.observer.logServerInfo(h.connIndex, registrationDetails.Location, nil, fmt.Sprintf("Connection %s registered", registrationDetails.UUID))
|
||||
h.observer.sendConnectedEvent(h.connIndex, H2mux, registrationDetails.Location)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -42,9 +42,3 @@ func (sc *errClosedSession) Error() string {
|
||||
return fmt.Sprintf("session closed by local due to %s", sc.message)
|
||||
}
|
||||
}
|
||||
|
||||
// newDatagram is an event when transport receives new datagram
|
||||
type newDatagram struct {
|
||||
sessionID uuid.UUID
|
||||
payload []byte
|
||||
}
|
||||
|
||||
+78
-58
@@ -2,13 +2,14 @@ package datagramsession
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/rs/zerolog"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/cloudflare/cloudflared/packet"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -16,6 +17,10 @@ const (
|
||||
defaultReqTimeout = time.Second * 5
|
||||
)
|
||||
|
||||
var (
|
||||
errSessionManagerClosed = fmt.Errorf("session manager closed")
|
||||
)
|
||||
|
||||
// Manager defines the APIs to manage sessions from the same transport.
|
||||
type Manager interface {
|
||||
// Serve starts the event loop
|
||||
@@ -24,73 +29,70 @@ type Manager interface {
|
||||
RegisterSession(ctx context.Context, sessionID uuid.UUID, dstConn io.ReadWriteCloser) (*Session, error)
|
||||
// UnregisterSession stops tracking the session and terminates it
|
||||
UnregisterSession(ctx context.Context, sessionID uuid.UUID, message string, byRemote bool) error
|
||||
// UpdateLogger updates the logger used by the Manager
|
||||
UpdateLogger(log *zerolog.Logger)
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
registrationChan chan *registerSessionEvent
|
||||
unregistrationChan chan *unregisterSessionEvent
|
||||
datagramChan chan *newDatagram
|
||||
transport transport
|
||||
sendFunc transportSender
|
||||
receiveChan <-chan *packet.Session
|
||||
closedChan <-chan struct{}
|
||||
sessions map[uuid.UUID]*Session
|
||||
log *zerolog.Logger
|
||||
// timeout waiting for an API to finish. This can be overriden in test
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func NewManager(transport transport, log *zerolog.Logger) *manager {
|
||||
func NewManager(log *zerolog.Logger, sendF transportSender, receiveChan <-chan *packet.Session) *manager {
|
||||
return &manager{
|
||||
registrationChan: make(chan *registerSessionEvent),
|
||||
unregistrationChan: make(chan *unregisterSessionEvent),
|
||||
// datagramChan is buffered, so it can read more datagrams from transport while the event loop is processing other events
|
||||
datagramChan: make(chan *newDatagram, requestChanCapacity),
|
||||
transport: transport,
|
||||
sessions: make(map[uuid.UUID]*Session),
|
||||
log: log,
|
||||
timeout: defaultReqTimeout,
|
||||
sendFunc: sendF,
|
||||
receiveChan: receiveChan,
|
||||
closedChan: make(chan struct{}),
|
||||
sessions: make(map[uuid.UUID]*Session),
|
||||
log: log,
|
||||
timeout: defaultReqTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manager) UpdateLogger(log *zerolog.Logger) {
|
||||
// Benign data race, no problem if the old pointer is read or not concurrently.
|
||||
m.log = log
|
||||
}
|
||||
|
||||
func (m *manager) Serve(ctx context.Context) error {
|
||||
errGroup, ctx := errgroup.WithContext(ctx)
|
||||
errGroup.Go(func() error {
|
||||
for {
|
||||
sessionID, payload, err := m.transport.ReceiveFrom()
|
||||
if err != nil {
|
||||
if aerr, ok := err.(*quic.ApplicationError); ok && uint64(aerr.ErrorCode) == uint64(quic.NoError) {
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
datagram := &newDatagram{
|
||||
sessionID: sessionID,
|
||||
payload: payload,
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
// Only the event loop routine can update/lookup the sessions map to avoid concurrent access
|
||||
// Send the datagram to the event loop. It will find the session to send to
|
||||
case m.datagramChan <- datagram:
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
m.shutdownSessions(ctx.Err())
|
||||
return ctx.Err()
|
||||
// receiveChan is buffered, so the transport can read more datagrams from transport while the event loop is
|
||||
// processing other events
|
||||
case datagram := <-m.receiveChan:
|
||||
m.sendToSession(datagram)
|
||||
case registration := <-m.registrationChan:
|
||||
m.registerSession(ctx, registration)
|
||||
case unregistration := <-m.unregistrationChan:
|
||||
m.unregisterSession(unregistration)
|
||||
}
|
||||
})
|
||||
errGroup.Go(func() error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case datagram := <-m.datagramChan:
|
||||
m.sendToSession(datagram)
|
||||
case registration := <-m.registrationChan:
|
||||
m.registerSession(ctx, registration)
|
||||
// TODO: TUN-5422: Unregister inactive session upon timeout
|
||||
case unregistration := <-m.unregistrationChan:
|
||||
m.unregisterSession(unregistration)
|
||||
}
|
||||
}
|
||||
})
|
||||
return errGroup.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manager) shutdownSessions(err error) {
|
||||
if err == nil {
|
||||
err = errSessionManagerClosed
|
||||
}
|
||||
closeSessionErr := &errClosedSession{
|
||||
message: err.Error(),
|
||||
// Usually connection with remote has been closed, so set this to true to skip unregistering from remote
|
||||
byRemote: true,
|
||||
}
|
||||
for _, s := range m.sessions {
|
||||
s.close(closeSessionErr)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manager) RegisterSession(ctx context.Context, sessionID uuid.UUID, originProxy io.ReadWriteCloser) (*Session, error) {
|
||||
@@ -104,15 +106,34 @@ func (m *manager) RegisterSession(ctx context.Context, sessionID uuid.UUID, orig
|
||||
case m.registrationChan <- event:
|
||||
session := <-event.resultChan
|
||||
return session, nil
|
||||
// Once closedChan is closed, manager won't accept more registration because nothing is
|
||||
// reading from registrationChan and it's an unbuffered channel
|
||||
case <-m.closedChan:
|
||||
return nil, errSessionManagerClosed
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manager) registerSession(ctx context.Context, registration *registerSessionEvent) {
|
||||
session := newSession(registration.sessionID, m.transport, registration.originProxy, m.log)
|
||||
session := m.newSession(registration.sessionID, registration.originProxy)
|
||||
m.sessions[registration.sessionID] = session
|
||||
registration.resultChan <- session
|
||||
}
|
||||
|
||||
func (m *manager) newSession(id uuid.UUID, dstConn io.ReadWriteCloser) *Session {
|
||||
logger := m.log.With().Str("sessionID", id.String()).Logger()
|
||||
return &Session{
|
||||
ID: id,
|
||||
sendFunc: m.sendFunc,
|
||||
dstConn: dstConn,
|
||||
// activeAtChan has low capacity. It can be full when there are many concurrent read/write. markActive() will
|
||||
// drop instead of blocking because last active time only needs to be an approximation
|
||||
activeAtChan: make(chan time.Time, 2),
|
||||
// capacity is 2 because close() and dstToTransport routine in Serve() can write to this channel
|
||||
closeChan: make(chan error, 2),
|
||||
log: &logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manager) UnregisterSession(ctx context.Context, sessionID uuid.UUID, message string, byRemote bool) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, m.timeout)
|
||||
defer cancel()
|
||||
@@ -129,6 +150,8 @@ func (m *manager) UnregisterSession(ctx context.Context, sessionID uuid.UUID, me
|
||||
return ctx.Err()
|
||||
case m.unregistrationChan <- event:
|
||||
return nil
|
||||
case <-m.closedChan:
|
||||
return errSessionManagerClosed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,16 +163,13 @@ func (m *manager) unregisterSession(unregistration *unregisterSessionEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manager) sendToSession(datagram *newDatagram) {
|
||||
session, ok := m.sessions[datagram.sessionID]
|
||||
func (m *manager) sendToSession(datagram *packet.Session) {
|
||||
session, ok := m.sessions[datagram.ID]
|
||||
if !ok {
|
||||
m.log.Error().Str("sessionID", datagram.sessionID.String()).Msg("session not found")
|
||||
m.log.Error().Str("sessionID", datagram.ID.String()).Msg("session not found")
|
||||
return
|
||||
}
|
||||
// session writes to destination over a connected UDP socket, which should not be blocking, so this call doesn't
|
||||
// need to run in another go routine
|
||||
_, err := session.transportToDst(datagram.payload)
|
||||
if err != nil {
|
||||
m.log.Err(err).Str("sessionID", datagram.sessionID.String()).Msg("Failed to write payload to session")
|
||||
}
|
||||
session.transportToDst(datagram.Payload)
|
||||
}
|
||||
|
||||
+143
-116
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -13,26 +14,30 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/cloudflare/cloudflared/packet"
|
||||
)
|
||||
|
||||
var (
|
||||
nopLogger = zerolog.Nop()
|
||||
)
|
||||
|
||||
func TestManagerServe(t *testing.T) {
|
||||
const (
|
||||
sessions = 20
|
||||
msgs = 50
|
||||
sessions = 2
|
||||
msgs = 5
|
||||
remoteUnregisterMsg = "eyeball closed connection"
|
||||
)
|
||||
log := zerolog.Nop()
|
||||
transport := &mockQUICTransport{
|
||||
reqChan: newDatagramChannel(1),
|
||||
respChan: newDatagramChannel(1),
|
||||
}
|
||||
mg := NewManager(transport, &log)
|
||||
|
||||
eyeballTracker := make(map[uuid.UUID]*datagramChannel)
|
||||
for i := 0; i < sessions; i++ {
|
||||
sessionID := uuid.New()
|
||||
eyeballTracker[sessionID] = newDatagramChannel(1)
|
||||
requestChan := make(chan *packet.Session)
|
||||
transport := mockQUICTransport{
|
||||
sessions: make(map[uuid.UUID]chan []byte),
|
||||
}
|
||||
for i := 0; i < sessions; i++ {
|
||||
transport.sessions[uuid.New()] = make(chan []byte)
|
||||
}
|
||||
|
||||
mg := NewManager(&nopLogger, transport.MuxSession, requestChan)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
serveDone := make(chan struct{})
|
||||
@@ -41,53 +46,42 @@ func TestManagerServe(t *testing.T) {
|
||||
close(serveDone)
|
||||
}(ctx)
|
||||
|
||||
go func(ctx context.Context) {
|
||||
for {
|
||||
sessionID, payload, err := transport.respChan.Receive(ctx)
|
||||
if err != nil {
|
||||
require.Equal(t, context.Canceled, err)
|
||||
return
|
||||
}
|
||||
respChan := eyeballTracker[sessionID]
|
||||
require.NoError(t, respChan.Send(ctx, sessionID, payload))
|
||||
}
|
||||
}(ctx)
|
||||
|
||||
errGroup, ctx := errgroup.WithContext(ctx)
|
||||
for sID, receiver := range eyeballTracker {
|
||||
for sessionID, eyeballRespChan := range transport.sessions {
|
||||
// Assign loop variables to local variables
|
||||
sID := sessionID
|
||||
payload := testPayload(sID)
|
||||
expectResp := testResponse(payload)
|
||||
|
||||
cfdConn, originConn := net.Pipe()
|
||||
|
||||
origin := mockOrigin{
|
||||
expectMsgCount: msgs,
|
||||
expectedMsg: payload,
|
||||
expectedResp: expectResp,
|
||||
conn: originConn,
|
||||
}
|
||||
|
||||
eyeball := mockEyeballSession{
|
||||
id: sID,
|
||||
expectedMsgCount: msgs,
|
||||
expectedMsg: payload,
|
||||
expectedResponse: expectResp,
|
||||
respReceiver: eyeballRespChan,
|
||||
}
|
||||
|
||||
// Assign loop variables to local variables
|
||||
sessionID := sID
|
||||
eyeballRespReceiver := receiver
|
||||
errGroup.Go(func() error {
|
||||
payload := testPayload(sessionID)
|
||||
expectResp := testResponse(payload)
|
||||
|
||||
cfdConn, originConn := net.Pipe()
|
||||
|
||||
origin := mockOrigin{
|
||||
expectMsgCount: msgs,
|
||||
expectedMsg: payload,
|
||||
expectedResp: expectResp,
|
||||
conn: originConn,
|
||||
}
|
||||
eyeball := mockEyeball{
|
||||
expectMsgCount: msgs,
|
||||
expectedMsg: expectResp,
|
||||
expectSessionID: sessionID,
|
||||
respReceiver: eyeballRespReceiver,
|
||||
}
|
||||
|
||||
session, err := mg.RegisterSession(ctx, sID, cfdConn)
|
||||
require.NoError(t, err)
|
||||
reqErrGroup, reqCtx := errgroup.WithContext(ctx)
|
||||
reqErrGroup.Go(func() error {
|
||||
return origin.serve()
|
||||
})
|
||||
reqErrGroup.Go(func() error {
|
||||
return eyeball.serve(reqCtx)
|
||||
return eyeball.serve(reqCtx, requestChan)
|
||||
})
|
||||
|
||||
session, err := mg.RegisterSession(ctx, sessionID, cfdConn)
|
||||
require.NoError(t, err)
|
||||
|
||||
sessionDone := make(chan struct{})
|
||||
go func() {
|
||||
closedByRemote, err := session.Serve(ctx, time.Minute*2)
|
||||
@@ -100,23 +94,17 @@ func TestManagerServe(t *testing.T) {
|
||||
close(sessionDone)
|
||||
}()
|
||||
|
||||
for i := 0; i < msgs; i++ {
|
||||
require.NoError(t, transport.newRequest(ctx, sessionID, testPayload(sessionID)))
|
||||
}
|
||||
|
||||
// Make sure eyeball and origin have received all messages before unregistering the session
|
||||
require.NoError(t, reqErrGroup.Wait())
|
||||
|
||||
require.NoError(t, mg.UnregisterSession(ctx, sessionID, remoteUnregisterMsg, true))
|
||||
require.NoError(t, mg.UnregisterSession(ctx, sID, remoteUnregisterMsg, true))
|
||||
<-sessionDone
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
require.NoError(t, errGroup.Wait())
|
||||
cancel()
|
||||
transport.close()
|
||||
<-serveDone
|
||||
}
|
||||
|
||||
@@ -124,12 +112,8 @@ func TestTimeout(t *testing.T) {
|
||||
const (
|
||||
testTimeout = time.Millisecond * 50
|
||||
)
|
||||
log := zerolog.Nop()
|
||||
transport := &mockQUICTransport{
|
||||
reqChan: newDatagramChannel(1),
|
||||
respChan: newDatagramChannel(1),
|
||||
}
|
||||
mg := NewManager(transport, &log)
|
||||
|
||||
mg := NewManager(&nopLogger, nil, nil)
|
||||
mg.timeout = testTimeout
|
||||
ctx := context.Background()
|
||||
sessionID := uuid.New()
|
||||
@@ -142,6 +126,80 @@ func TestTimeout(t *testing.T) {
|
||||
require.ErrorIs(t, err, context.DeadlineExceeded)
|
||||
}
|
||||
|
||||
func TestUnregisterSessionCloseSession(t *testing.T) {
|
||||
sessionID := uuid.New()
|
||||
payload := []byte(t.Name())
|
||||
sender := newMockTransportSender(sessionID, payload)
|
||||
mg := NewManager(&nopLogger, sender.muxSession, nil)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
managerDone := make(chan struct{})
|
||||
go func() {
|
||||
err := mg.Serve(ctx)
|
||||
require.Error(t, err)
|
||||
close(managerDone)
|
||||
}()
|
||||
|
||||
cfdConn, originConn := net.Pipe()
|
||||
session, err := mg.RegisterSession(ctx, sessionID, cfdConn)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, session)
|
||||
|
||||
unregisteredChan := make(chan struct{})
|
||||
go func() {
|
||||
_, err := originConn.Write(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = mg.UnregisterSession(ctx, sessionID, "eyeball closed session", true)
|
||||
require.NoError(t, err)
|
||||
|
||||
close(unregisteredChan)
|
||||
}()
|
||||
|
||||
closedByRemote, err := session.Serve(ctx, time.Minute)
|
||||
require.True(t, closedByRemote)
|
||||
require.Error(t, err)
|
||||
|
||||
<-unregisteredChan
|
||||
cancel()
|
||||
<-managerDone
|
||||
}
|
||||
|
||||
func TestManagerCtxDoneCloseSessions(t *testing.T) {
|
||||
sessionID := uuid.New()
|
||||
payload := []byte(t.Name())
|
||||
sender := newMockTransportSender(sessionID, payload)
|
||||
mg := NewManager(&nopLogger, sender.muxSession, nil)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := mg.Serve(ctx)
|
||||
require.Error(t, err)
|
||||
}()
|
||||
|
||||
cfdConn, originConn := net.Pipe()
|
||||
session, err := mg.RegisterSession(ctx, sessionID, cfdConn)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, session)
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, err := originConn.Write(payload)
|
||||
require.NoError(t, err)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
closedByRemote, err := session.Serve(ctx, time.Minute)
|
||||
require.False(t, closedByRemote)
|
||||
require.Error(t, err)
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
type mockOrigin struct {
|
||||
expectMsgCount int
|
||||
expectedMsg []byte
|
||||
@@ -163,7 +221,6 @@ func (mo *mockOrigin) serve() error {
|
||||
if !bytes.Equal(readBuffer[:n], mo.expectedMsg) {
|
||||
return fmt.Errorf("Expect %v, read %v", mo.expectedMsg, readBuffer[:n])
|
||||
}
|
||||
|
||||
_, err = mo.conn.Write(mo.expectedResp)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -180,65 +237,35 @@ func testResponse(msg []byte) []byte {
|
||||
return []byte(fmt.Sprintf("Response to %v", msg))
|
||||
}
|
||||
|
||||
type mockEyeball struct {
|
||||
expectMsgCount int
|
||||
expectedMsg []byte
|
||||
expectSessionID uuid.UUID
|
||||
respReceiver *datagramChannel
|
||||
type mockQUICTransport struct {
|
||||
sessions map[uuid.UUID]chan []byte
|
||||
}
|
||||
|
||||
func (me *mockEyeball) serve(ctx context.Context) error {
|
||||
for i := 0; i < me.expectMsgCount; i++ {
|
||||
sessionID, msg, err := me.respReceiver.Receive(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sessionID != me.expectSessionID {
|
||||
return fmt.Errorf("Expect session %s, got %s", me.expectSessionID, sessionID)
|
||||
}
|
||||
if !bytes.Equal(msg, me.expectedMsg) {
|
||||
return fmt.Errorf("Expect %v, read %v", me.expectedMsg, msg)
|
||||
}
|
||||
}
|
||||
func (me *mockQUICTransport) MuxSession(session *packet.Session) error {
|
||||
s := me.sessions[session.ID]
|
||||
s <- session.Payload
|
||||
return nil
|
||||
}
|
||||
|
||||
// datagramChannel is a channel for Datagram with wrapper to send/receive with context
|
||||
type datagramChannel struct {
|
||||
datagramChan chan *newDatagram
|
||||
closedChan chan struct{}
|
||||
type mockEyeballSession struct {
|
||||
id uuid.UUID
|
||||
expectedMsgCount int
|
||||
expectedMsg []byte
|
||||
expectedResponse []byte
|
||||
respReceiver <-chan []byte
|
||||
}
|
||||
|
||||
func newDatagramChannel(capacity uint) *datagramChannel {
|
||||
return &datagramChannel{
|
||||
datagramChan: make(chan *newDatagram, capacity),
|
||||
closedChan: make(chan struct{}),
|
||||
func (me *mockEyeballSession) serve(ctx context.Context, requestChan chan *packet.Session) error {
|
||||
for i := 0; i < me.expectedMsgCount; i++ {
|
||||
requestChan <- &packet.Session{
|
||||
ID: me.id,
|
||||
Payload: me.expectedMsg,
|
||||
}
|
||||
resp := <-me.respReceiver
|
||||
if !bytes.Equal(resp, me.expectedResponse) {
|
||||
return fmt.Errorf("Expect %v, read %v", me.expectedResponse, resp)
|
||||
}
|
||||
fmt.Println("Resp", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *datagramChannel) Send(ctx context.Context, sessionID uuid.UUID, payload []byte) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-rc.closedChan:
|
||||
return fmt.Errorf("datagram channel closed")
|
||||
case rc.datagramChan <- &newDatagram{sessionID: sessionID, payload: payload}:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *datagramChannel) Receive(ctx context.Context) (uuid.UUID, []byte, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return uuid.Nil, nil, ctx.Err()
|
||||
case <-rc.closedChan:
|
||||
return uuid.Nil, nil, fmt.Errorf("datagram channel closed")
|
||||
case msg := <-rc.datagramChan:
|
||||
return msg.sessionID, msg.payload, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *datagramChannel) Close() {
|
||||
// No need to close msgChan, it will be garbage collect once there is no reference to it
|
||||
close(rc.closedChan)
|
||||
return nil
|
||||
}
|
||||
|
||||
+48
-44
@@ -2,12 +2,16 @@ package datagramsession
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/packet"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -18,41 +22,33 @@ func SessionIdleErr(timeout time.Duration) error {
|
||||
return fmt.Errorf("session idle for %v", timeout)
|
||||
}
|
||||
|
||||
type transportSender func(session *packet.Session) error
|
||||
|
||||
// ErrVithVariableSeverity are errors that have variable severity
|
||||
type ErrVithVariableSeverity interface {
|
||||
error
|
||||
// LogLevel return the severity of this error
|
||||
LogLevel() zerolog.Level
|
||||
}
|
||||
|
||||
// Session is a bidirectional pipe of datagrams between transport and dstConn
|
||||
// Currently the only implementation of transport is quic DatagramMuxer
|
||||
// Destination can be a connection with origin or with eyeball
|
||||
// When the destination is origin:
|
||||
// - Datagrams from edge are read by Manager from the transport. Manager finds the corresponding Session and calls the
|
||||
// write method of the Session to send to origin
|
||||
// - Datagrams from origin are read from conn and SentTo transport. Transport will return them to eyeball
|
||||
// - Manager receives datagrams from receiveChan and calls the transportToDst method of the Session to send to origin
|
||||
// - Datagrams from origin are read from conn and Send to transport using the transportSender callback. Transport will return them to eyeball
|
||||
// When the destination is eyeball:
|
||||
// - Datagrams from eyeball are read from conn and SentTo transport. Transport will send them to cloudflared
|
||||
// - Datagrams from cloudflared are read by Manager from the transport. Manager finds the corresponding Session and calls the
|
||||
// write method of the Session to send to eyeball
|
||||
// - Datagrams from eyeball are read from conn and Send to transport. Transport will send them to cloudflared using the transportSender callback.
|
||||
// - Manager receives datagrams from receiveChan and calls the transportToDst method of the Session to send to the eyeball
|
||||
type Session struct {
|
||||
ID uuid.UUID
|
||||
transport transport
|
||||
dstConn io.ReadWriteCloser
|
||||
ID uuid.UUID
|
||||
sendFunc transportSender
|
||||
dstConn io.ReadWriteCloser
|
||||
// activeAtChan is used to communicate the last read/write time
|
||||
activeAtChan chan time.Time
|
||||
closeChan chan error
|
||||
log *zerolog.Logger
|
||||
}
|
||||
|
||||
func newSession(id uuid.UUID, transport transport, dstConn io.ReadWriteCloser, log *zerolog.Logger) *Session {
|
||||
return &Session{
|
||||
ID: id,
|
||||
transport: transport,
|
||||
dstConn: dstConn,
|
||||
// activeAtChan has low capacity. It can be full when there are many concurrent read/write. markActive() will
|
||||
// drop instead of blocking because last active time only needs to be an approximation
|
||||
activeAtChan: make(chan time.Time, 2),
|
||||
// capacity is 2 because close() and dstToTransport routine in Serve() can write to this channel
|
||||
closeChan: make(chan error, 2),
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) Serve(ctx context.Context, closeAfterIdle time.Duration) (closedByRemote bool, err error) {
|
||||
go func() {
|
||||
// QUIC implementation copies data to another buffer before returning https://github.com/lucas-clemente/quic-go/blob/v0.24.0/session.go#L1967-L1975
|
||||
@@ -60,9 +56,20 @@ func (s *Session) Serve(ctx context.Context, closeAfterIdle time.Duration) (clos
|
||||
const maxPacketSize = 1500
|
||||
readBuffer := make([]byte, maxPacketSize)
|
||||
for {
|
||||
if err := s.dstToTransport(readBuffer); err != nil {
|
||||
s.closeChan <- err
|
||||
return
|
||||
if closeSession, err := s.dstToTransport(readBuffer); err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
s.log.Debug().Msg("Destination connection closed")
|
||||
} else {
|
||||
level := zerolog.ErrorLevel
|
||||
if variableErr, ok := err.(ErrVithVariableSeverity); ok {
|
||||
level = variableErr.LogLevel()
|
||||
}
|
||||
s.log.WithLevel(level).Err(err).Msg("Failed to send session payload from destination to transport")
|
||||
}
|
||||
if closeSession {
|
||||
s.closeChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -103,32 +110,29 @@ func (s *Session) waitForCloseCondition(ctx context.Context, closeAfterIdle time
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) dstToTransport(buffer []byte) error {
|
||||
func (s *Session) dstToTransport(buffer []byte) (closeSession bool, err error) {
|
||||
n, err := s.dstConn.Read(buffer)
|
||||
s.markActive()
|
||||
// https://pkg.go.dev/io#Reader suggests caller should always process n > 0 bytes
|
||||
if n > 0 {
|
||||
if n <= int(s.transport.MTU()) {
|
||||
err = s.transport.SendTo(s.ID, buffer[:n])
|
||||
} else {
|
||||
// drop packet for now, eventually reply with ICMP for PMTUD
|
||||
s.log.Debug().
|
||||
Str("session", s.ID.String()).
|
||||
Int("len", n).
|
||||
Int("mtu", s.transport.MTU()).
|
||||
Msg("dropped packet exceeding MTU")
|
||||
if n > 0 || err == nil {
|
||||
session := packet.Session{
|
||||
ID: s.ID,
|
||||
Payload: buffer[:n],
|
||||
}
|
||||
if sendErr := s.sendFunc(&session); sendErr != nil {
|
||||
return false, sendErr
|
||||
}
|
||||
}
|
||||
// Some UDP application might send 0-size payload.
|
||||
if err == nil && n == 0 {
|
||||
err = s.transport.SendTo(s.ID, []byte{})
|
||||
}
|
||||
return err
|
||||
return err != nil, err
|
||||
}
|
||||
|
||||
func (s *Session) transportToDst(payload []byte) (int, error) {
|
||||
s.markActive()
|
||||
return s.dstConn.Write(payload)
|
||||
n, err := s.dstConn.Write(payload)
|
||||
if err != nil {
|
||||
s.log.Err(err).Msg("Failed to write payload to session")
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Sends the last active time to the idle checker loop without blocking. activeAtChan will only be full when there
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/cloudflare/cloudflared/packet"
|
||||
)
|
||||
|
||||
// TestCloseSession makes sure a session will stop after context is done
|
||||
@@ -41,12 +43,10 @@ func testSessionReturns(t *testing.T, closeBy closeMethod, closeAfterIdle time.D
|
||||
sessionID := uuid.New()
|
||||
cfdConn, originConn := net.Pipe()
|
||||
payload := testPayload(sessionID)
|
||||
transport := &mockQUICTransport{
|
||||
reqChan: newDatagramChannel(1),
|
||||
respChan: newDatagramChannel(1),
|
||||
}
|
||||
|
||||
log := zerolog.Nop()
|
||||
session := newSession(sessionID, transport, cfdConn, &log)
|
||||
mg := NewManager(&log, nil, nil)
|
||||
session := mg.newSession(sessionID, cfdConn)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
sessionDone := make(chan struct{})
|
||||
@@ -117,12 +117,11 @@ func testActiveSessionNotClosed(t *testing.T, readFromDst bool, writeToDst bool)
|
||||
sessionID := uuid.New()
|
||||
cfdConn, originConn := net.Pipe()
|
||||
payload := testPayload(sessionID)
|
||||
transport := &mockQUICTransport{
|
||||
reqChan: newDatagramChannel(100),
|
||||
respChan: newDatagramChannel(100),
|
||||
}
|
||||
log := zerolog.Nop()
|
||||
session := newSession(sessionID, transport, cfdConn, &log)
|
||||
|
||||
respChan := make(chan *packet.Session)
|
||||
sender := newMockTransportSender(sessionID, payload)
|
||||
mg := NewManager(&nopLogger, sender.muxSession, respChan)
|
||||
session := mg.newSession(sessionID, cfdConn)
|
||||
|
||||
startTime := time.Now()
|
||||
activeUntil := startTime.Add(activeTime)
|
||||
@@ -184,7 +183,8 @@ func testActiveSessionNotClosed(t *testing.T, readFromDst bool, writeToDst bool)
|
||||
|
||||
func TestMarkActiveNotBlocking(t *testing.T) {
|
||||
const concurrentCalls = 50
|
||||
session := newSession(uuid.New(), nil, nil, nil)
|
||||
mg := NewManager(&nopLogger, nil, nil)
|
||||
session := mg.newSession(uuid.New(), nil)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(concurrentCalls)
|
||||
for i := 0; i < concurrentCalls; i++ {
|
||||
@@ -196,15 +196,17 @@ func TestMarkActiveNotBlocking(t *testing.T) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Some UDP application might send 0-size payload.
|
||||
func TestZeroBytePayload(t *testing.T) {
|
||||
sessionID := uuid.New()
|
||||
cfdConn, originConn := net.Pipe()
|
||||
transport := &mockQUICTransport{
|
||||
reqChan: newDatagramChannel(1),
|
||||
respChan: newDatagramChannel(1),
|
||||
|
||||
sender := sendOnceTransportSender{
|
||||
baseSender: newMockTransportSender(sessionID, make([]byte, 0)),
|
||||
sentChan: make(chan struct{}),
|
||||
}
|
||||
log := zerolog.Nop()
|
||||
session := newSession(sessionID, transport, cfdConn, &log)
|
||||
mg := NewManager(&nopLogger, sender.muxSession, nil)
|
||||
session := mg.newSession(sessionID, cfdConn)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
errGroup, ctx := errgroup.WithContext(ctx)
|
||||
@@ -224,11 +226,39 @@ func TestZeroBytePayload(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
|
||||
receivedSessionID, payload, err := transport.respChan.Receive(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, payload, 0)
|
||||
require.Equal(t, sessionID, receivedSessionID)
|
||||
|
||||
<-sender.sentChan
|
||||
cancel()
|
||||
require.NoError(t, errGroup.Wait())
|
||||
}
|
||||
|
||||
type mockTransportSender struct {
|
||||
expectedSessionID uuid.UUID
|
||||
expectedPayload []byte
|
||||
}
|
||||
|
||||
func newMockTransportSender(expectedSessionID uuid.UUID, expectedPayload []byte) *mockTransportSender {
|
||||
return &mockTransportSender{
|
||||
expectedSessionID: expectedSessionID,
|
||||
expectedPayload: expectedPayload,
|
||||
}
|
||||
}
|
||||
|
||||
func (mts *mockTransportSender) muxSession(session *packet.Session) error {
|
||||
if session.ID != mts.expectedSessionID {
|
||||
return fmt.Errorf("Expect session %s, got %s", mts.expectedSessionID, session.ID)
|
||||
}
|
||||
if !bytes.Equal(session.Payload, mts.expectedPayload) {
|
||||
return fmt.Errorf("Expect %v, read %v", mts.expectedPayload, session.Payload)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type sendOnceTransportSender struct {
|
||||
baseSender *mockTransportSender
|
||||
sentChan chan struct{}
|
||||
}
|
||||
|
||||
func (sots *sendOnceTransportSender) muxSession(session *packet.Session) error {
|
||||
defer close(sots.sentChan)
|
||||
return sots.baseSender.muxSession(session)
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package datagramsession
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
// Transport is a connection between cloudflared and edge that can multiplex datagrams from multiple sessions
|
||||
type transport interface {
|
||||
// SendTo writes payload for a session to the transport
|
||||
SendTo(sessionID uuid.UUID, payload []byte) error
|
||||
// ReceiveFrom reads the next datagram from the transport
|
||||
ReceiveFrom() (uuid.UUID, []byte, error)
|
||||
// Max transmission unit to receive from the transport
|
||||
MTU() int
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package datagramsession
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type mockQUICTransport struct {
|
||||
reqChan *datagramChannel
|
||||
respChan *datagramChannel
|
||||
}
|
||||
|
||||
func (mt *mockQUICTransport) SendTo(sessionID uuid.UUID, payload []byte) error {
|
||||
buf := make([]byte, len(payload))
|
||||
// The QUIC implementation copies data to another buffer before returning https://github.com/lucas-clemente/quic-go/blob/v0.24.0/session.go#L1967-L1975
|
||||
copy(buf, payload)
|
||||
return mt.respChan.Send(context.Background(), sessionID, buf)
|
||||
}
|
||||
|
||||
func (mt *mockQUICTransport) ReceiveFrom() (uuid.UUID, []byte, error) {
|
||||
return mt.reqChan.Receive(context.Background())
|
||||
}
|
||||
|
||||
func (mt *mockQUICTransport) MTU() int {
|
||||
return 1280
|
||||
}
|
||||
|
||||
func (mt *mockQUICTransport) newRequest(ctx context.Context, sessionID uuid.UUID, payload []byte) error {
|
||||
return mt.reqChan.Send(ctx, sessionID, payload)
|
||||
}
|
||||
|
||||
func (mt *mockQUICTransport) close() {
|
||||
mt.reqChan.Close()
|
||||
mt.respChan.Close()
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.17.5 as builder
|
||||
FROM golang:1.19 as builder
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0
|
||||
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
@@ -6,4 +6,4 @@ RUN apt-get update
|
||||
COPY . .
|
||||
# compile cloudflared
|
||||
RUN make cloudflared
|
||||
RUN cp /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
||||
RUN cp /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package allregions
|
||||
|
||||
// Region contains cloudflared edge addresses. The edge is partitioned into several regions for
|
||||
// redundancy purposes.
|
||||
type AddrSet map[*EdgeAddr]UsedBy
|
||||
|
||||
// AddrUsedBy finds the address used by the given connection in this region.
|
||||
// Returns nil if the connection isn't using any IP.
|
||||
func (a AddrSet) AddrUsedBy(connID int) *EdgeAddr {
|
||||
for addr, used := range a {
|
||||
if used.Used && used.ConnID == connID {
|
||||
return addr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AvailableAddrs counts how many unused addresses this region contains.
|
||||
func (a AddrSet) AvailableAddrs() int {
|
||||
n := 0
|
||||
for _, usedby := range a {
|
||||
if !usedby.Used {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// GetUnusedIP returns a random unused address in this region.
|
||||
// Returns nil if all addresses are in use.
|
||||
func (a AddrSet) GetUnusedIP(excluding *EdgeAddr) *EdgeAddr {
|
||||
for addr, usedby := range a {
|
||||
if !usedby.Used && addr != excluding {
|
||||
return addr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use the address, assigning it to a proxy connection.
|
||||
func (a AddrSet) Use(addr *EdgeAddr, connID int) {
|
||||
if addr == nil {
|
||||
return
|
||||
}
|
||||
a[addr] = InUse(connID)
|
||||
}
|
||||
|
||||
// GetAnyAddress returns an arbitrary address from the region.
|
||||
func (a AddrSet) GetAnyAddress() *EdgeAddr {
|
||||
for addr := range a {
|
||||
return addr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GiveBack the address, ensuring it is no longer assigned to an IP.
|
||||
// Returns true if the address is in this region.
|
||||
func (a AddrSet) GiveBack(addr *EdgeAddr) (ok bool) {
|
||||
if _, ok := a[addr]; !ok {
|
||||
return false
|
||||
}
|
||||
a[addr] = Unused()
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package allregions
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAddrSet_AddrUsedBy(t *testing.T) {
|
||||
type args struct {
|
||||
connID int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
addrSet AddrSet
|
||||
args args
|
||||
want *EdgeAddr
|
||||
}{
|
||||
{
|
||||
name: "happy trivial test",
|
||||
addrSet: AddrSet{
|
||||
&addr0: InUse(0),
|
||||
},
|
||||
args: args{connID: 0},
|
||||
want: &addr0,
|
||||
},
|
||||
{
|
||||
name: "sad trivial test",
|
||||
addrSet: AddrSet{
|
||||
&addr0: InUse(0),
|
||||
},
|
||||
args: args{connID: 1},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "sad test",
|
||||
addrSet: AddrSet{
|
||||
&addr0: InUse(0),
|
||||
&addr1: InUse(1),
|
||||
&addr2: InUse(2),
|
||||
},
|
||||
args: args{connID: 3},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "happy test",
|
||||
addrSet: AddrSet{
|
||||
&addr0: InUse(0),
|
||||
&addr1: InUse(1),
|
||||
&addr2: InUse(2),
|
||||
},
|
||||
args: args{connID: 1},
|
||||
want: &addr1,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.addrSet.AddrUsedBy(tt.args.connID); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Region.AddrUsedBy() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddrSet_AvailableAddrs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
addrSet AddrSet
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "contains addresses",
|
||||
addrSet: AddrSet{
|
||||
&addr0: InUse(0),
|
||||
&addr1: Unused(),
|
||||
&addr2: InUse(2),
|
||||
},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "all free",
|
||||
addrSet: AddrSet{
|
||||
&addr0: Unused(),
|
||||
&addr1: Unused(),
|
||||
&addr2: Unused(),
|
||||
},
|
||||
want: 3,
|
||||
},
|
||||
{
|
||||
name: "all used",
|
||||
addrSet: AddrSet{
|
||||
&addr0: InUse(0),
|
||||
&addr1: InUse(1),
|
||||
&addr2: InUse(2),
|
||||
},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
addrSet: AddrSet{},
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.addrSet.AvailableAddrs(); got != tt.want {
|
||||
t.Errorf("Region.AvailableAddrs() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddrSet_GetUnusedIP(t *testing.T) {
|
||||
type args struct {
|
||||
excluding *EdgeAddr
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
addrSet AddrSet
|
||||
args args
|
||||
want *EdgeAddr
|
||||
}{
|
||||
{
|
||||
name: "happy test with excluding set",
|
||||
addrSet: AddrSet{
|
||||
&addr0: Unused(),
|
||||
&addr1: Unused(),
|
||||
&addr2: InUse(2),
|
||||
},
|
||||
args: args{excluding: &addr0},
|
||||
want: &addr1,
|
||||
},
|
||||
{
|
||||
name: "happy test with no excluding",
|
||||
addrSet: AddrSet{
|
||||
&addr0: InUse(0),
|
||||
&addr1: Unused(),
|
||||
&addr2: InUse(2),
|
||||
},
|
||||
args: args{excluding: nil},
|
||||
want: &addr1,
|
||||
},
|
||||
{
|
||||
name: "sad test with no excluding",
|
||||
addrSet: AddrSet{
|
||||
&addr0: InUse(0),
|
||||
&addr1: InUse(1),
|
||||
&addr2: InUse(2),
|
||||
},
|
||||
args: args{excluding: nil},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "sad test with excluding",
|
||||
addrSet: AddrSet{
|
||||
&addr0: Unused(),
|
||||
&addr1: InUse(1),
|
||||
&addr2: InUse(2),
|
||||
},
|
||||
args: args{excluding: &addr0},
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.addrSet.GetUnusedIP(tt.args.excluding); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Region.GetUnusedIP() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddrSet_GiveBack(t *testing.T) {
|
||||
type args struct {
|
||||
addr *EdgeAddr
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
addrSet AddrSet
|
||||
args args
|
||||
wantOk bool
|
||||
availableAfter int
|
||||
}{
|
||||
{
|
||||
name: "sad test with excluding",
|
||||
addrSet: AddrSet{
|
||||
&addr1: InUse(1),
|
||||
},
|
||||
args: args{addr: &addr1},
|
||||
wantOk: true,
|
||||
availableAfter: 1,
|
||||
},
|
||||
{
|
||||
name: "sad test with excluding",
|
||||
addrSet: AddrSet{
|
||||
&addr1: InUse(1),
|
||||
},
|
||||
args: args{addr: &addr2},
|
||||
wantOk: false,
|
||||
availableAfter: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotOk := tt.addrSet.GiveBack(tt.args.addr); gotOk != tt.wantOk {
|
||||
t.Errorf("Region.GiveBack() = %v, want %v", gotOk, tt.wantOk)
|
||||
}
|
||||
if tt.availableAfter != tt.addrSet.AvailableAddrs() {
|
||||
t.Errorf("Region.AvailableAddrs() = %v, want %v", tt.addrSet.AvailableAddrs(), tt.availableAfter)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddrSet_GetAnyAddress(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
addrSet AddrSet
|
||||
wantNil bool
|
||||
}{
|
||||
{
|
||||
name: "Sad test -- GetAnyAddress should only fail if the region is empty",
|
||||
addrSet: AddrSet{},
|
||||
wantNil: true,
|
||||
},
|
||||
{
|
||||
name: "Happy test (all addresses unused)",
|
||||
addrSet: AddrSet{
|
||||
&addr0: Unused(),
|
||||
},
|
||||
wantNil: false,
|
||||
},
|
||||
{
|
||||
name: "Happy test (GetAnyAddress can still return addresses used by proxy conns)",
|
||||
addrSet: AddrSet{
|
||||
&addr0: InUse(2),
|
||||
},
|
||||
wantNil: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.addrSet.GetAnyAddress(); tt.wantNil != (got == nil) {
|
||||
t.Errorf("Region.GetAnyAddress() = %v, but should it return nil? %v", got, tt.wantNil)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
const (
|
||||
// Used to discover HA origintunneld servers
|
||||
srvService = "origintunneld"
|
||||
srvService = "v2-origintunneld"
|
||||
srvProto = "tcp"
|
||||
srvName = "argotunnel.com"
|
||||
|
||||
@@ -32,20 +32,53 @@ var (
|
||||
netLookupIP = net.LookupIP
|
||||
)
|
||||
|
||||
// ConfigIPVersion is the selection of IP versions from config
|
||||
type ConfigIPVersion int8
|
||||
|
||||
const (
|
||||
Auto ConfigIPVersion = 2
|
||||
IPv4Only ConfigIPVersion = 4
|
||||
IPv6Only ConfigIPVersion = 6
|
||||
)
|
||||
|
||||
// IPVersion is the IP version of an EdgeAddr
|
||||
type EdgeIPVersion int8
|
||||
|
||||
const (
|
||||
V4 EdgeIPVersion = 4
|
||||
V6 EdgeIPVersion = 6
|
||||
)
|
||||
|
||||
// String returns the enum's constant name.
|
||||
func (c EdgeIPVersion) String() string {
|
||||
switch c {
|
||||
case V4:
|
||||
return "4"
|
||||
case V6:
|
||||
return "6"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// EdgeAddr is a representation of possible ways to refer an edge location.
|
||||
type EdgeAddr struct {
|
||||
TCP *net.TCPAddr
|
||||
UDP *net.UDPAddr
|
||||
TCP *net.TCPAddr
|
||||
UDP *net.UDPAddr
|
||||
IPVersion EdgeIPVersion
|
||||
}
|
||||
|
||||
// If the call to net.LookupSRV fails, try to fall back to DoT from Cloudflare directly.
|
||||
//
|
||||
// Note: Instead of DoT, we could also have used DoH. Either of these:
|
||||
// - directly via the JSON API (https://1.1.1.1/dns-query?ct=application/dns-json&name=_origintunneld._tcp.argotunnel.com&type=srv)
|
||||
// - indirectly via `tunneldns.NewUpstreamHTTPS()`
|
||||
// - directly via the JSON API (https://1.1.1.1/dns-query?ct=application/dns-json&name=_origintunneld._tcp.argotunnel.com&type=srv)
|
||||
// - indirectly via `tunneldns.NewUpstreamHTTPS()`
|
||||
//
|
||||
// But both of these cases miss out on a key feature from the stdlib:
|
||||
// "The returned records are sorted by priority and randomized by weight within a priority."
|
||||
// (https://golang.org/pkg/net/#Resolver.LookupSRV)
|
||||
//
|
||||
// "The returned records are sorted by priority and randomized by weight within a priority."
|
||||
// (https://golang.org/pkg/net/#Resolver.LookupSRV)
|
||||
//
|
||||
// Does this matter? I don't know. It may someday. Let's use DoT so we don't need to worry about it.
|
||||
// See also: Go feature request for stdlib-supported DoH: https://github.com/golang/go/issues/27552
|
||||
var fallbackLookupSRV = lookupSRVWithDOT
|
||||
@@ -85,6 +118,9 @@ func edgeDiscovery(log *zerolog.Logger, srvService string) ([][]*EdgeAddr, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, e := range edgeAddrs {
|
||||
log.Debug().Msgf("Edge Address: %+v", *e)
|
||||
}
|
||||
resolvedAddrPerCNAME = append(resolvedAddrPerCNAME, edgeAddrs)
|
||||
}
|
||||
|
||||
@@ -120,9 +156,14 @@ func resolveSRV(srv *net.SRV) ([]*EdgeAddr, error) {
|
||||
}
|
||||
addrs := make([]*EdgeAddr, len(ips))
|
||||
for i, ip := range ips {
|
||||
version := V6
|
||||
if ip.To4() != nil {
|
||||
version = V4
|
||||
}
|
||||
addrs[i] = &EdgeAddr{
|
||||
TCP: &net.TCPAddr{IP: ip, Port: int(srv.Port)},
|
||||
UDP: &net.UDPAddr{IP: ip, Port: int(srv.Port)},
|
||||
TCP: &net.TCPAddr{IP: ip, Port: int(srv.Port)},
|
||||
UDP: &net.UDPAddr{IP: ip, Port: int(srv.Port)},
|
||||
IPVersion: version,
|
||||
}
|
||||
}
|
||||
return addrs, nil
|
||||
@@ -143,11 +184,15 @@ func ResolveAddrs(addrs []string, log *zerolog.Logger) (resolved []*EdgeAddr) {
|
||||
log.Error().Str(logFieldAddress, addr).Err(err).Msg("failed to resolve to UDP address")
|
||||
continue
|
||||
}
|
||||
version := V6
|
||||
if udpAddr.IP.To4() != nil {
|
||||
version = V4
|
||||
}
|
||||
resolved = append(resolved, &EdgeAddr{
|
||||
TCP: tcpAddr,
|
||||
UDP: udpAddr,
|
||||
TCP: tcpAddr,
|
||||
UDP: udpAddr,
|
||||
IPVersion: version,
|
||||
})
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -9,6 +9,115 @@ import (
|
||||
"testing/quick"
|
||||
)
|
||||
|
||||
var (
|
||||
v4Addrs = []*EdgeAddr{&addr0, &addr1, &addr2, &addr3}
|
||||
v6Addrs = []*EdgeAddr{&addr4, &addr5, &addr6, &addr7}
|
||||
addr0 = EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("123.4.5.0"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("123.4.5.0"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: V4,
|
||||
}
|
||||
addr1 = EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("123.4.5.1"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("123.4.5.1"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: V4,
|
||||
}
|
||||
addr2 = EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("123.4.5.2"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("123.4.5.2"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: V4,
|
||||
}
|
||||
addr3 = EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("123.4.5.3"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("123.4.5.3"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: V4,
|
||||
}
|
||||
addr4 = EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::1"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::1"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: V6,
|
||||
}
|
||||
addr5 = EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::2"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::2"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: V6,
|
||||
}
|
||||
addr6 = EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::3"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::3"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: V6,
|
||||
}
|
||||
addr7 = EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::4"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::4"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: V6,
|
||||
}
|
||||
)
|
||||
|
||||
type mockAddrs struct {
|
||||
// a set of synthetic SRV records
|
||||
addrMap map[net.SRV][]*EdgeAddr
|
||||
@@ -36,7 +145,7 @@ func newMockAddrs(port uint16, numRegions uint8, numAddrsPerRegion uint8) mockAd
|
||||
IP: net.ParseIP(fmt.Sprintf("10.0.%v.%v", r, a)),
|
||||
Port: int(port),
|
||||
}
|
||||
addrs = append(addrs, &EdgeAddr{tcpAddr, udpAddr})
|
||||
addrs = append(addrs, &EdgeAddr{tcpAddr, udpAddr, V4})
|
||||
}
|
||||
addrMap[srv] = addrs
|
||||
numAddrs += len(addrs)
|
||||
|
||||
@@ -1,79 +1,155 @@
|
||||
package allregions
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
timeoutDuration = 10 * time.Minute
|
||||
)
|
||||
|
||||
// Region contains cloudflared edge addresses. The edge is partitioned into several regions for
|
||||
// redundancy purposes.
|
||||
type Region struct {
|
||||
connFor map[*EdgeAddr]UsedBy
|
||||
primaryIsActive bool
|
||||
active AddrSet
|
||||
primary AddrSet
|
||||
secondary AddrSet
|
||||
primaryTimeout time.Time
|
||||
timeoutDuration time.Duration
|
||||
}
|
||||
|
||||
// NewRegion creates a region with the given addresses, which are all unused.
|
||||
func NewRegion(addrs []*EdgeAddr) Region {
|
||||
func NewRegion(addrs []*EdgeAddr, overrideIPVersion ConfigIPVersion) Region {
|
||||
// The zero value of UsedBy is Unused(), so we can just initialize the map's values with their
|
||||
// zero values.
|
||||
connFor := make(map[*EdgeAddr]UsedBy)
|
||||
for _, addr := range addrs {
|
||||
connFor[addr] = Unused()
|
||||
connForv4 := make(AddrSet)
|
||||
connForv6 := make(AddrSet)
|
||||
systemPreference := V6
|
||||
for i, addr := range addrs {
|
||||
if i == 0 {
|
||||
// First family of IPs returned is system preference of IP
|
||||
systemPreference = addr.IPVersion
|
||||
}
|
||||
switch addr.IPVersion {
|
||||
case V4:
|
||||
connForv4[addr] = Unused()
|
||||
case V6:
|
||||
connForv6[addr] = Unused()
|
||||
}
|
||||
}
|
||||
|
||||
// Process as system preference
|
||||
var primary AddrSet
|
||||
var secondary AddrSet
|
||||
switch systemPreference {
|
||||
case V4:
|
||||
primary = connForv4
|
||||
secondary = connForv6
|
||||
case V6:
|
||||
primary = connForv6
|
||||
secondary = connForv4
|
||||
}
|
||||
|
||||
// Override with provided preference
|
||||
switch overrideIPVersion {
|
||||
case IPv4Only:
|
||||
primary = connForv4
|
||||
secondary = make(AddrSet) // empty
|
||||
case IPv6Only:
|
||||
primary = connForv6
|
||||
secondary = make(AddrSet) // empty
|
||||
case Auto:
|
||||
// no change
|
||||
default:
|
||||
// no change
|
||||
}
|
||||
|
||||
return Region{
|
||||
connFor: connFor,
|
||||
primaryIsActive: true,
|
||||
active: primary,
|
||||
primary: primary,
|
||||
secondary: secondary,
|
||||
timeoutDuration: timeoutDuration,
|
||||
}
|
||||
}
|
||||
|
||||
// AddrUsedBy finds the address used by the given connection in this region.
|
||||
// Returns nil if the connection isn't using any IP.
|
||||
func (r *Region) AddrUsedBy(connID int) *EdgeAddr {
|
||||
for addr, used := range r.connFor {
|
||||
if used.Used && used.ConnID == connID {
|
||||
return addr
|
||||
}
|
||||
edgeAddr := r.primary.AddrUsedBy(connID)
|
||||
if edgeAddr == nil {
|
||||
edgeAddr = r.secondary.AddrUsedBy(connID)
|
||||
}
|
||||
return nil
|
||||
return edgeAddr
|
||||
}
|
||||
|
||||
// AvailableAddrs counts how many unused addresses this region contains.
|
||||
func (r Region) AvailableAddrs() int {
|
||||
n := 0
|
||||
for _, usedby := range r.connFor {
|
||||
if !usedby.Used {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
return r.active.AvailableAddrs()
|
||||
}
|
||||
|
||||
// GetUnusedIP returns a random unused address in this region.
|
||||
// Returns nil if all addresses are in use.
|
||||
func (r Region) GetUnusedIP(excluding *EdgeAddr) *EdgeAddr {
|
||||
for addr, usedby := range r.connFor {
|
||||
if !usedby.Used && addr != excluding {
|
||||
return addr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use the address, assigning it to a proxy connection.
|
||||
func (r Region) Use(addr *EdgeAddr, connID int) {
|
||||
if addr == nil {
|
||||
return
|
||||
}
|
||||
r.connFor[addr] = InUse(connID)
|
||||
}
|
||||
|
||||
// GetAnyAddress returns an arbitrary address from the region.
|
||||
func (r Region) GetAnyAddress() *EdgeAddr {
|
||||
for addr := range r.connFor {
|
||||
// AssignAnyAddress returns a random unused address in this region now
|
||||
// assigned to the connID excluding the provided EdgeAddr.
|
||||
// Returns nil if all addresses are in use for the region.
|
||||
func (r Region) AssignAnyAddress(connID int, excluding *EdgeAddr) *EdgeAddr {
|
||||
if addr := r.active.GetUnusedIP(excluding); addr != nil {
|
||||
r.active.Use(addr, connID)
|
||||
return addr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAnyAddress returns an arbitrary address from the region.
|
||||
func (r Region) GetAnyAddress() *EdgeAddr {
|
||||
return r.active.GetAnyAddress()
|
||||
}
|
||||
|
||||
// GiveBack the address, ensuring it is no longer assigned to an IP.
|
||||
// Returns true if the address is in this region.
|
||||
func (r Region) GiveBack(addr *EdgeAddr) (ok bool) {
|
||||
if _, ok := r.connFor[addr]; !ok {
|
||||
return false
|
||||
func (r *Region) GiveBack(addr *EdgeAddr, hasConnectivityError bool) (ok bool) {
|
||||
if ok = r.primary.GiveBack(addr); !ok {
|
||||
// Attempt to give back the address in the secondary set
|
||||
if ok = r.secondary.GiveBack(addr); !ok {
|
||||
// Address is not in this region
|
||||
return
|
||||
}
|
||||
}
|
||||
r.connFor[addr] = Unused()
|
||||
return true
|
||||
|
||||
// No connectivity error: no worry
|
||||
if !hasConnectivityError {
|
||||
return
|
||||
}
|
||||
|
||||
// If using primary and returned address is IPv6 and secondary is available
|
||||
if r.primaryIsActive && addr.IPVersion == V6 && len(r.secondary) > 0 {
|
||||
r.active = r.secondary
|
||||
r.primaryIsActive = false
|
||||
r.primaryTimeout = time.Now().Add(r.timeoutDuration)
|
||||
return
|
||||
}
|
||||
|
||||
// Do nothing for IPv4 or if secondary is empty
|
||||
if r.primaryIsActive {
|
||||
return
|
||||
}
|
||||
|
||||
// Immediately return to primary pool, regardless of current primary timeout
|
||||
if addr.IPVersion == V4 {
|
||||
activatePrimary(r)
|
||||
return
|
||||
}
|
||||
|
||||
// Timeout exceeded and can be reset to primary pool
|
||||
if r.primaryTimeout.Before(time.Now()) {
|
||||
activatePrimary(r)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// activatePrimary sets the primary set to the active set and resets the timeout.
|
||||
func activatePrimary(r *Region) {
|
||||
r.active = r.primary
|
||||
r.primaryIsActive = true
|
||||
r.primaryTimeout = time.Now() // reset timeout
|
||||
}
|
||||
|
||||
@@ -1,284 +1,357 @@
|
||||
package allregions
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func makeAddrSet(addrs []*EdgeAddr) AddrSet {
|
||||
addrSet := make(AddrSet, len(addrs))
|
||||
for _, addr := range addrs {
|
||||
addrSet[addr] = Unused()
|
||||
}
|
||||
return addrSet
|
||||
}
|
||||
|
||||
func TestRegion_New(t *testing.T) {
|
||||
r := NewRegion([]*EdgeAddr{&addr0, &addr1, &addr2})
|
||||
if r.AvailableAddrs() != 3 {
|
||||
t.Errorf("r.AvailableAddrs() == %v but want 3", r.AvailableAddrs())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegion_AddrUsedBy(t *testing.T) {
|
||||
type fields struct {
|
||||
connFor map[*EdgeAddr]UsedBy
|
||||
}
|
||||
type args struct {
|
||||
connID int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want *EdgeAddr
|
||||
name string
|
||||
addrs []*EdgeAddr
|
||||
mode ConfigIPVersion
|
||||
expectedAddrs int
|
||||
primary AddrSet
|
||||
secondary AddrSet
|
||||
}{
|
||||
{
|
||||
name: "happy trivial test",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr0: InUse(0),
|
||||
}},
|
||||
args: args{connID: 0},
|
||||
want: &addr0,
|
||||
name: "IPv4 addresses with IPv4Only",
|
||||
addrs: v4Addrs,
|
||||
mode: IPv4Only,
|
||||
expectedAddrs: len(v4Addrs),
|
||||
primary: makeAddrSet(v4Addrs),
|
||||
secondary: AddrSet{},
|
||||
},
|
||||
{
|
||||
name: "sad trivial test",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr0: InUse(0),
|
||||
}},
|
||||
args: args{connID: 1},
|
||||
want: nil,
|
||||
name: "IPv6 addresses with IPv4Only",
|
||||
addrs: v6Addrs,
|
||||
mode: IPv4Only,
|
||||
expectedAddrs: 0,
|
||||
primary: AddrSet{},
|
||||
secondary: AddrSet{},
|
||||
},
|
||||
{
|
||||
name: "sad test",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr0: InUse(0),
|
||||
&addr1: InUse(1),
|
||||
&addr2: InUse(2),
|
||||
}},
|
||||
args: args{connID: 3},
|
||||
want: nil,
|
||||
name: "IPv6 addresses with IPv6Only",
|
||||
addrs: v6Addrs,
|
||||
mode: IPv6Only,
|
||||
expectedAddrs: len(v6Addrs),
|
||||
primary: makeAddrSet(v6Addrs),
|
||||
secondary: AddrSet{},
|
||||
},
|
||||
{
|
||||
name: "happy test",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr0: InUse(0),
|
||||
&addr1: InUse(1),
|
||||
&addr2: InUse(2),
|
||||
}},
|
||||
args: args{connID: 1},
|
||||
want: &addr1,
|
||||
name: "IPv6 addresses with IPv4Only",
|
||||
addrs: v6Addrs,
|
||||
mode: IPv4Only,
|
||||
expectedAddrs: 0,
|
||||
primary: AddrSet{},
|
||||
secondary: AddrSet{},
|
||||
},
|
||||
{
|
||||
name: "IPv4 (first) and IPv6 addresses with Auto",
|
||||
addrs: append(v4Addrs, v6Addrs...),
|
||||
mode: Auto,
|
||||
expectedAddrs: len(v4Addrs),
|
||||
primary: makeAddrSet(v4Addrs),
|
||||
secondary: makeAddrSet(v6Addrs),
|
||||
},
|
||||
{
|
||||
name: "IPv6 (first) and IPv4 addresses with Auto",
|
||||
addrs: append(v6Addrs, v4Addrs...),
|
||||
mode: Auto,
|
||||
expectedAddrs: len(v6Addrs),
|
||||
primary: makeAddrSet(v6Addrs),
|
||||
secondary: makeAddrSet(v4Addrs),
|
||||
},
|
||||
{
|
||||
name: "IPv4 addresses with Auto",
|
||||
addrs: v4Addrs,
|
||||
mode: Auto,
|
||||
expectedAddrs: len(v4Addrs),
|
||||
primary: makeAddrSet(v4Addrs),
|
||||
secondary: AddrSet{},
|
||||
},
|
||||
{
|
||||
name: "IPv6 addresses with Auto",
|
||||
addrs: v6Addrs,
|
||||
mode: Auto,
|
||||
expectedAddrs: len(v6Addrs),
|
||||
primary: makeAddrSet(v6Addrs),
|
||||
secondary: AddrSet{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Region{
|
||||
connFor: tt.fields.connFor,
|
||||
r := NewRegion(tt.addrs, tt.mode)
|
||||
assert.Equal(t, tt.expectedAddrs, r.AvailableAddrs())
|
||||
assert.Equal(t, tt.primary, r.primary)
|
||||
assert.Equal(t, tt.secondary, r.secondary)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegion_AnyAddress_EmptyActiveSet(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
addrs []*EdgeAddr
|
||||
mode ConfigIPVersion
|
||||
}{
|
||||
{
|
||||
name: "IPv6 addresses with IPv4Only",
|
||||
addrs: v6Addrs,
|
||||
mode: IPv4Only,
|
||||
},
|
||||
{
|
||||
name: "IPv4 addresses with IPv6Only",
|
||||
addrs: v4Addrs,
|
||||
mode: IPv6Only,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := NewRegion(tt.addrs, tt.mode)
|
||||
addr := r.GetAnyAddress()
|
||||
assert.Nil(t, addr)
|
||||
addr = r.AssignAnyAddress(0, nil)
|
||||
assert.Nil(t, addr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegion_AssignAnyAddress_FullyUsedActiveSet(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
addrs []*EdgeAddr
|
||||
mode ConfigIPVersion
|
||||
}{
|
||||
{
|
||||
name: "IPv6 addresses with IPv6Only",
|
||||
addrs: v6Addrs,
|
||||
mode: IPv6Only,
|
||||
},
|
||||
{
|
||||
name: "IPv4 addresses with IPv4Only",
|
||||
addrs: v4Addrs,
|
||||
mode: IPv4Only,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := NewRegion(tt.addrs, tt.mode)
|
||||
total := r.active.AvailableAddrs()
|
||||
for i := 0; i < total; i++ {
|
||||
addr := r.AssignAnyAddress(i, nil)
|
||||
assert.NotNil(t, addr)
|
||||
}
|
||||
if got := r.AddrUsedBy(tt.args.connID); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Region.AddrUsedBy() = %v, want %v", got, tt.want)
|
||||
addr := r.AssignAnyAddress(9, nil)
|
||||
assert.Nil(t, addr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var giveBackTests = []struct {
|
||||
name string
|
||||
addrs []*EdgeAddr
|
||||
mode ConfigIPVersion
|
||||
expectedAddrs int
|
||||
primary AddrSet
|
||||
secondary AddrSet
|
||||
primarySwap bool
|
||||
}{
|
||||
{
|
||||
name: "IPv4 addresses with IPv4Only",
|
||||
addrs: v4Addrs,
|
||||
mode: IPv4Only,
|
||||
expectedAddrs: len(v4Addrs),
|
||||
primary: makeAddrSet(v4Addrs),
|
||||
secondary: AddrSet{},
|
||||
primarySwap: false,
|
||||
},
|
||||
{
|
||||
name: "IPv6 addresses with IPv6Only",
|
||||
addrs: v6Addrs,
|
||||
mode: IPv6Only,
|
||||
expectedAddrs: len(v6Addrs),
|
||||
primary: makeAddrSet(v6Addrs),
|
||||
secondary: AddrSet{},
|
||||
primarySwap: false,
|
||||
},
|
||||
{
|
||||
name: "IPv4 (first) and IPv6 addresses with Auto",
|
||||
addrs: append(v4Addrs, v6Addrs...),
|
||||
mode: Auto,
|
||||
expectedAddrs: len(v4Addrs),
|
||||
primary: makeAddrSet(v4Addrs),
|
||||
secondary: makeAddrSet(v6Addrs),
|
||||
primarySwap: false,
|
||||
},
|
||||
{
|
||||
name: "IPv6 (first) and IPv4 addresses with Auto",
|
||||
addrs: append(v6Addrs, v4Addrs...),
|
||||
mode: Auto,
|
||||
expectedAddrs: len(v6Addrs),
|
||||
primary: makeAddrSet(v6Addrs),
|
||||
secondary: makeAddrSet(v4Addrs),
|
||||
primarySwap: true,
|
||||
},
|
||||
{
|
||||
name: "IPv4 addresses with Auto",
|
||||
addrs: v4Addrs,
|
||||
mode: Auto,
|
||||
expectedAddrs: len(v4Addrs),
|
||||
primary: makeAddrSet(v4Addrs),
|
||||
secondary: AddrSet{},
|
||||
primarySwap: false,
|
||||
},
|
||||
{
|
||||
name: "IPv6 addresses with Auto",
|
||||
addrs: v6Addrs,
|
||||
mode: Auto,
|
||||
expectedAddrs: len(v6Addrs),
|
||||
primary: makeAddrSet(v6Addrs),
|
||||
secondary: AddrSet{},
|
||||
primarySwap: false,
|
||||
},
|
||||
}
|
||||
|
||||
func TestRegion_GiveBack_NoConnectivityError(t *testing.T) {
|
||||
for _, tt := range giveBackTests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := NewRegion(tt.addrs, tt.mode)
|
||||
addr := r.AssignAnyAddress(0, nil)
|
||||
assert.NotNil(t, addr)
|
||||
assert.True(t, r.GiveBack(addr, false))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegion_GiveBack_ForeignAddr(t *testing.T) {
|
||||
invalid := EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("123.4.5.0"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("123.4.5.0"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: V4,
|
||||
}
|
||||
for _, tt := range giveBackTests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := NewRegion(tt.addrs, tt.mode)
|
||||
assert.False(t, r.GiveBack(&invalid, false))
|
||||
assert.False(t, r.GiveBack(&invalid, true))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegion_GiveBack_SwapPrimary(t *testing.T) {
|
||||
for _, tt := range giveBackTests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := NewRegion(tt.addrs, tt.mode)
|
||||
addr := r.AssignAnyAddress(0, nil)
|
||||
assert.NotNil(t, addr)
|
||||
assert.True(t, r.GiveBack(addr, true))
|
||||
assert.Equal(t, tt.primarySwap, !r.primaryIsActive)
|
||||
if tt.primarySwap {
|
||||
assert.Equal(t, r.secondary, r.active)
|
||||
assert.False(t, r.primaryTimeout.IsZero())
|
||||
} else {
|
||||
assert.Equal(t, r.primary, r.active)
|
||||
assert.True(t, r.primaryTimeout.IsZero())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegion_AvailableAddrs(t *testing.T) {
|
||||
type fields struct {
|
||||
connFor map[*EdgeAddr]UsedBy
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "contains addresses",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr0: InUse(0),
|
||||
&addr1: Unused(),
|
||||
&addr2: InUse(2),
|
||||
}},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "all free",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr0: Unused(),
|
||||
&addr1: Unused(),
|
||||
&addr2: Unused(),
|
||||
}},
|
||||
want: 3,
|
||||
},
|
||||
{
|
||||
name: "all used",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr0: InUse(0),
|
||||
&addr1: InUse(1),
|
||||
&addr2: InUse(2),
|
||||
}},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{}},
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := Region{
|
||||
connFor: tt.fields.connFor,
|
||||
}
|
||||
if got := r.AvailableAddrs(); got != tt.want {
|
||||
t.Errorf("Region.AvailableAddrs() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
func TestRegion_GiveBack_IPv4_ResetPrimary(t *testing.T) {
|
||||
r := NewRegion(append(v6Addrs, v4Addrs...), Auto)
|
||||
// Exhaust all IPv6 addresses
|
||||
a0 := r.AssignAnyAddress(0, nil)
|
||||
a1 := r.AssignAnyAddress(1, nil)
|
||||
a2 := r.AssignAnyAddress(2, nil)
|
||||
a3 := r.AssignAnyAddress(3, nil)
|
||||
assert.NotNil(t, a0)
|
||||
assert.NotNil(t, a1)
|
||||
assert.NotNil(t, a2)
|
||||
assert.NotNil(t, a3)
|
||||
// Give back the first IPv6 address to fallback to secondary IPv4 address set
|
||||
assert.True(t, r.GiveBack(a0, true))
|
||||
assert.False(t, r.primaryIsActive)
|
||||
// Give back another IPv6 address
|
||||
assert.True(t, r.GiveBack(a1, true))
|
||||
// Primary shouldn't change
|
||||
assert.False(t, r.primaryIsActive)
|
||||
// Request an address (should be IPv4 from secondary)
|
||||
a4_v4 := r.AssignAnyAddress(4, nil)
|
||||
assert.NotNil(t, a4_v4)
|
||||
assert.Equal(t, V4, a4_v4.IPVersion)
|
||||
a5_v4 := r.AssignAnyAddress(5, nil)
|
||||
assert.NotNil(t, a5_v4)
|
||||
assert.Equal(t, V4, a5_v4.IPVersion)
|
||||
a6_v4 := r.AssignAnyAddress(6, nil)
|
||||
assert.NotNil(t, a6_v4)
|
||||
assert.Equal(t, V4, a6_v4.IPVersion)
|
||||
// Return IPv4 address (without failure)
|
||||
// Primary shouldn't change because it is not a connectivity failure
|
||||
assert.True(t, r.GiveBack(a4_v4, false))
|
||||
assert.False(t, r.primaryIsActive)
|
||||
// Return IPv4 address (with failure)
|
||||
// Primary should change because it is a connectivity failure
|
||||
assert.True(t, r.GiveBack(a5_v4, true))
|
||||
assert.True(t, r.primaryIsActive)
|
||||
// Return IPv4 address (with failure)
|
||||
// Primary shouldn't change because the address is returned to the inactive
|
||||
// secondary address set
|
||||
assert.True(t, r.GiveBack(a6_v4, true))
|
||||
assert.True(t, r.primaryIsActive)
|
||||
// Return IPv6 address (without failure)
|
||||
// Primary shoudn't change because it is not a connectivity failure
|
||||
assert.True(t, r.GiveBack(a2, false))
|
||||
assert.True(t, r.primaryIsActive)
|
||||
}
|
||||
|
||||
func TestRegion_GetUnusedIP(t *testing.T) {
|
||||
type fields struct {
|
||||
connFor map[*EdgeAddr]UsedBy
|
||||
}
|
||||
type args struct {
|
||||
excluding *EdgeAddr
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want *EdgeAddr
|
||||
}{
|
||||
{
|
||||
name: "happy test with excluding set",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr0: Unused(),
|
||||
&addr1: Unused(),
|
||||
&addr2: InUse(2),
|
||||
}},
|
||||
args: args{excluding: &addr0},
|
||||
want: &addr1,
|
||||
},
|
||||
{
|
||||
name: "happy test with no excluding",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr0: InUse(0),
|
||||
&addr1: Unused(),
|
||||
&addr2: InUse(2),
|
||||
}},
|
||||
args: args{excluding: nil},
|
||||
want: &addr1,
|
||||
},
|
||||
{
|
||||
name: "sad test with no excluding",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr0: InUse(0),
|
||||
&addr1: InUse(1),
|
||||
&addr2: InUse(2),
|
||||
}},
|
||||
args: args{excluding: nil},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "sad test with excluding",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr0: Unused(),
|
||||
&addr1: InUse(1),
|
||||
&addr2: InUse(2),
|
||||
}},
|
||||
args: args{excluding: &addr0},
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := Region{
|
||||
connFor: tt.fields.connFor,
|
||||
}
|
||||
if got := r.GetUnusedIP(tt.args.excluding); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Region.GetUnusedIP() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegion_GiveBack(t *testing.T) {
|
||||
type fields struct {
|
||||
connFor map[*EdgeAddr]UsedBy
|
||||
}
|
||||
type args struct {
|
||||
addr *EdgeAddr
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantOk bool
|
||||
availableAfter int
|
||||
}{
|
||||
{
|
||||
name: "sad test with excluding",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr1: InUse(1),
|
||||
}},
|
||||
args: args{addr: &addr1},
|
||||
wantOk: true,
|
||||
availableAfter: 1,
|
||||
},
|
||||
{
|
||||
name: "sad test with excluding",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr1: InUse(1),
|
||||
}},
|
||||
args: args{addr: &addr2},
|
||||
wantOk: false,
|
||||
availableAfter: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := Region{
|
||||
connFor: tt.fields.connFor,
|
||||
}
|
||||
if gotOk := r.GiveBack(tt.args.addr); gotOk != tt.wantOk {
|
||||
t.Errorf("Region.GiveBack() = %v, want %v", gotOk, tt.wantOk)
|
||||
}
|
||||
if tt.availableAfter != r.AvailableAddrs() {
|
||||
t.Errorf("Region.AvailableAddrs() = %v, want %v", r.AvailableAddrs(), tt.availableAfter)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegion_GetAnyAddress(t *testing.T) {
|
||||
type fields struct {
|
||||
connFor map[*EdgeAddr]UsedBy
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wantNil bool
|
||||
}{
|
||||
{
|
||||
name: "Sad test -- GetAnyAddress should only fail if the region is empty",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{}},
|
||||
wantNil: true,
|
||||
},
|
||||
{
|
||||
name: "Happy test (all addresses unused)",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr0: Unused(),
|
||||
}},
|
||||
wantNil: false,
|
||||
},
|
||||
{
|
||||
name: "Happy test (GetAnyAddress can still return addresses used by proxy conns)",
|
||||
fields: fields{connFor: map[*EdgeAddr]UsedBy{
|
||||
&addr0: InUse(2),
|
||||
}},
|
||||
wantNil: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := Region{
|
||||
connFor: tt.fields.connFor,
|
||||
}
|
||||
if got := r.GetAnyAddress(); tt.wantNil != (got == nil) {
|
||||
t.Errorf("Region.GetAnyAddress() = %v, but should it return nil? %v", got, tt.wantNil)
|
||||
}
|
||||
})
|
||||
}
|
||||
func TestRegion_GiveBack_Timeout(t *testing.T) {
|
||||
r := NewRegion(append(v6Addrs, v4Addrs...), Auto)
|
||||
a0 := r.AssignAnyAddress(0, nil)
|
||||
a1 := r.AssignAnyAddress(1, nil)
|
||||
a2 := r.AssignAnyAddress(2, nil)
|
||||
assert.NotNil(t, a0)
|
||||
assert.NotNil(t, a1)
|
||||
assert.NotNil(t, a2)
|
||||
// Give back IPv6 address to set timeout
|
||||
assert.True(t, r.GiveBack(a0, true))
|
||||
assert.False(t, r.primaryIsActive)
|
||||
assert.False(t, r.primaryTimeout.IsZero())
|
||||
// Request an address (should be IPv4 from secondary)
|
||||
a3_v4 := r.AssignAnyAddress(3, nil)
|
||||
assert.NotNil(t, a3_v4)
|
||||
assert.Equal(t, V4, a3_v4.IPVersion)
|
||||
assert.False(t, r.primaryIsActive)
|
||||
// Give back IPv6 address inside timeout (no change)
|
||||
assert.True(t, r.GiveBack(a2, true))
|
||||
assert.False(t, r.primaryIsActive)
|
||||
assert.False(t, r.primaryTimeout.IsZero())
|
||||
// Accelerate timeout
|
||||
r.primaryTimeout = time.Now().Add(-time.Minute)
|
||||
// Return IPv6 address
|
||||
assert.True(t, r.GiveBack(a1, true))
|
||||
assert.True(t, r.primaryIsActive)
|
||||
// Returning an IPv4 address after primary is active shouldn't change primary
|
||||
// even with a connectivity error
|
||||
assert.True(t, r.GiveBack(a3_v4, true))
|
||||
assert.True(t, r.primaryIsActive)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package allregions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
@@ -18,7 +19,7 @@ type Regions struct {
|
||||
// ------------------------------------
|
||||
|
||||
// ResolveEdge resolves the Cloudflare edge, returning all regions discovered.
|
||||
func ResolveEdge(log *zerolog.Logger, region string) (*Regions, error) {
|
||||
func ResolveEdge(log *zerolog.Logger, region string, overrideIPVersion ConfigIPVersion) (*Regions, error) {
|
||||
edgeAddrs, err := edgeDiscovery(log, getRegionalServiceName(region))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -27,8 +28,8 @@ func ResolveEdge(log *zerolog.Logger, region string) (*Regions, error) {
|
||||
return nil, fmt.Errorf("expected at least 2 Cloudflare Regions regions, but SRV only returned %v", len(edgeAddrs))
|
||||
}
|
||||
return &Regions{
|
||||
region1: NewRegion(edgeAddrs[0]),
|
||||
region2: NewRegion(edgeAddrs[1]),
|
||||
region1: NewRegion(edgeAddrs[0], overrideIPVersion),
|
||||
region2: NewRegion(edgeAddrs[1], overrideIPVersion),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -56,8 +57,8 @@ func NewNoResolve(addrs []*EdgeAddr) *Regions {
|
||||
}
|
||||
|
||||
return &Regions{
|
||||
region1: NewRegion(region1),
|
||||
region2: NewRegion(region2),
|
||||
region1: NewRegion(region1, Auto),
|
||||
region2: NewRegion(region2, Auto),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +86,15 @@ func (rs *Regions) AddrUsedBy(connID int) *EdgeAddr {
|
||||
// GetUnusedAddr gets an unused addr from the edge, excluding the given addr. Prefer to use addresses
|
||||
// evenly across both regions.
|
||||
func (rs *Regions) GetUnusedAddr(excluding *EdgeAddr, connID int) *EdgeAddr {
|
||||
// If both regions have the same number of available addrs, lets randomise which one
|
||||
// we pick. The rest of this algorithm will continue to make sure we always use addresses
|
||||
// evenly across both regions.
|
||||
if rs.region1.AvailableAddrs() == rs.region2.AvailableAddrs() {
|
||||
regions := []Region{rs.region1, rs.region2}
|
||||
firstChoice := rand.Intn(2)
|
||||
return getAddrs(excluding, connID, ®ions[firstChoice], ®ions[1-firstChoice])
|
||||
}
|
||||
|
||||
if rs.region1.AvailableAddrs() > rs.region2.AvailableAddrs() {
|
||||
return getAddrs(excluding, connID, &rs.region1, &rs.region2)
|
||||
}
|
||||
@@ -95,14 +105,12 @@ func (rs *Regions) GetUnusedAddr(excluding *EdgeAddr, connID int) *EdgeAddr {
|
||||
// getAddrs tries to grab address form `first` region, then `second` region
|
||||
// this is an unrolled loop over 2 element array
|
||||
func getAddrs(excluding *EdgeAddr, connID int, first *Region, second *Region) *EdgeAddr {
|
||||
addr := first.GetUnusedIP(excluding)
|
||||
addr := first.AssignAnyAddress(connID, excluding)
|
||||
if addr != nil {
|
||||
first.Use(addr, connID)
|
||||
return addr
|
||||
}
|
||||
addr = second.GetUnusedIP(excluding)
|
||||
addr = second.AssignAnyAddress(connID, excluding)
|
||||
if addr != nil {
|
||||
second.Use(addr, connID)
|
||||
return addr
|
||||
}
|
||||
|
||||
@@ -116,18 +124,18 @@ func (rs *Regions) AvailableAddrs() int {
|
||||
|
||||
// GiveBack the address so that other connections can use it.
|
||||
// Returns true if the address is in this edge.
|
||||
func (rs *Regions) GiveBack(addr *EdgeAddr) bool {
|
||||
if found := rs.region1.GiveBack(addr); found {
|
||||
func (rs *Regions) GiveBack(addr *EdgeAddr, hasConnectivityError bool) bool {
|
||||
if found := rs.region1.GiveBack(addr, hasConnectivityError); found {
|
||||
return found
|
||||
}
|
||||
return rs.region2.GiveBack(addr)
|
||||
return rs.region2.GiveBack(addr, hasConnectivityError)
|
||||
}
|
||||
|
||||
// Return regionalized service name if `region` isn't empty, otherwise return the global service name for origintunneld
|
||||
func getRegionalServiceName(region string) string {
|
||||
if region != "" {
|
||||
return region + "-" + srvService // Example: `us-origintunneld`
|
||||
return region + "-" + srvService // Example: `us-v2-origintunneld`
|
||||
}
|
||||
|
||||
return srvService // Global service is just `origintunneld`
|
||||
return srvService // Global service is just `v2-origintunneld`
|
||||
}
|
||||
|
||||
@@ -1,134 +1,215 @@
|
||||
package allregions
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
addr0 = EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("123.4.5.0"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("123.4.5.0"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
func makeRegions(addrs []*EdgeAddr, mode ConfigIPVersion) Regions {
|
||||
r1addrs := make([]*EdgeAddr, 0)
|
||||
r2addrs := make([]*EdgeAddr, 0)
|
||||
for i, addr := range addrs {
|
||||
if i%2 == 0 {
|
||||
r1addrs = append(r1addrs, addr)
|
||||
} else {
|
||||
r2addrs = append(r2addrs, addr)
|
||||
}
|
||||
}
|
||||
addr1 = EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("123.4.5.1"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("123.4.5.1"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
}
|
||||
addr2 = EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("123.4.5.2"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("123.4.5.2"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
}
|
||||
addr3 = EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("123.4.5.3"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("123.4.5.3"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func makeRegions() Regions {
|
||||
r1 := NewRegion([]*EdgeAddr{&addr0, &addr1})
|
||||
r2 := NewRegion([]*EdgeAddr{&addr2, &addr3})
|
||||
r1 := NewRegion(r1addrs, mode)
|
||||
r2 := NewRegion(r2addrs, mode)
|
||||
return Regions{region1: r1, region2: r2}
|
||||
}
|
||||
|
||||
func TestRegions_AddrUsedBy(t *testing.T) {
|
||||
rs := makeRegions()
|
||||
addr1 := rs.GetUnusedAddr(nil, 1)
|
||||
assert.Equal(t, addr1, rs.AddrUsedBy(1))
|
||||
addr2 := rs.GetUnusedAddr(nil, 2)
|
||||
assert.Equal(t, addr2, rs.AddrUsedBy(2))
|
||||
addr3 := rs.GetUnusedAddr(nil, 3)
|
||||
assert.Equal(t, addr3, rs.AddrUsedBy(3))
|
||||
tests := []struct {
|
||||
name string
|
||||
addrs []*EdgeAddr
|
||||
mode ConfigIPVersion
|
||||
}{
|
||||
{
|
||||
name: "IPv4 addresses with IPv4Only",
|
||||
addrs: v4Addrs,
|
||||
mode: IPv4Only,
|
||||
},
|
||||
{
|
||||
name: "IPv6 addresses with IPv6Only",
|
||||
addrs: v6Addrs,
|
||||
mode: IPv6Only,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rs := makeRegions(tt.addrs, tt.mode)
|
||||
addr1 := rs.GetUnusedAddr(nil, 1)
|
||||
assert.Equal(t, addr1, rs.AddrUsedBy(1))
|
||||
addr2 := rs.GetUnusedAddr(nil, 2)
|
||||
assert.Equal(t, addr2, rs.AddrUsedBy(2))
|
||||
addr3 := rs.GetUnusedAddr(nil, 3)
|
||||
assert.Equal(t, addr3, rs.AddrUsedBy(3))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegions_Giveback_Region1(t *testing.T) {
|
||||
rs := makeRegions()
|
||||
rs.region1.Use(&addr0, 0)
|
||||
rs.region1.Use(&addr1, 1)
|
||||
rs.region2.Use(&addr2, 2)
|
||||
rs.region2.Use(&addr3, 3)
|
||||
tests := []struct {
|
||||
name string
|
||||
addrs []*EdgeAddr
|
||||
mode ConfigIPVersion
|
||||
}{
|
||||
{
|
||||
name: "IPv4 addresses with IPv4Only",
|
||||
addrs: v4Addrs,
|
||||
mode: IPv4Only,
|
||||
},
|
||||
{
|
||||
name: "IPv6 addresses with IPv6Only",
|
||||
addrs: v6Addrs,
|
||||
mode: IPv6Only,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rs := makeRegions(tt.addrs, tt.mode)
|
||||
addr := rs.region1.AssignAnyAddress(0, nil)
|
||||
rs.region1.AssignAnyAddress(1, nil)
|
||||
rs.region2.AssignAnyAddress(2, nil)
|
||||
rs.region2.AssignAnyAddress(3, nil)
|
||||
|
||||
assert.Equal(t, 0, rs.AvailableAddrs())
|
||||
assert.Equal(t, 0, rs.AvailableAddrs())
|
||||
|
||||
rs.GiveBack(&addr0)
|
||||
assert.Equal(t, &addr0, rs.GetUnusedAddr(nil, 3))
|
||||
rs.GiveBack(addr, false)
|
||||
assert.Equal(t, addr, rs.GetUnusedAddr(nil, 0))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegions_Giveback_Region2(t *testing.T) {
|
||||
rs := makeRegions()
|
||||
rs.region1.Use(&addr0, 0)
|
||||
rs.region1.Use(&addr1, 1)
|
||||
rs.region2.Use(&addr2, 2)
|
||||
rs.region2.Use(&addr3, 3)
|
||||
tests := []struct {
|
||||
name string
|
||||
addrs []*EdgeAddr
|
||||
mode ConfigIPVersion
|
||||
}{
|
||||
{
|
||||
name: "IPv4 addresses with IPv4Only",
|
||||
addrs: v4Addrs,
|
||||
mode: IPv4Only,
|
||||
},
|
||||
{
|
||||
name: "IPv6 addresses with IPv6Only",
|
||||
addrs: v6Addrs,
|
||||
mode: IPv6Only,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rs := makeRegions(tt.addrs, tt.mode)
|
||||
rs.region1.AssignAnyAddress(0, nil)
|
||||
rs.region1.AssignAnyAddress(1, nil)
|
||||
addr := rs.region2.AssignAnyAddress(2, nil)
|
||||
rs.region2.AssignAnyAddress(3, nil)
|
||||
|
||||
assert.Equal(t, 0, rs.AvailableAddrs())
|
||||
assert.Equal(t, 0, rs.AvailableAddrs())
|
||||
|
||||
rs.GiveBack(&addr2)
|
||||
assert.Equal(t, &addr2, rs.GetUnusedAddr(nil, 2))
|
||||
rs.GiveBack(addr, false)
|
||||
assert.Equal(t, addr, rs.GetUnusedAddr(nil, 2))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegions_GetUnusedAddr_OneAddrLeft(t *testing.T) {
|
||||
rs := makeRegions()
|
||||
tests := []struct {
|
||||
name string
|
||||
addrs []*EdgeAddr
|
||||
mode ConfigIPVersion
|
||||
}{
|
||||
{
|
||||
name: "IPv4 addresses with IPv4Only",
|
||||
addrs: v4Addrs,
|
||||
mode: IPv4Only,
|
||||
},
|
||||
{
|
||||
name: "IPv6 addresses with IPv6Only",
|
||||
addrs: v6Addrs,
|
||||
mode: IPv6Only,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rs := makeRegions(tt.addrs, tt.mode)
|
||||
rs.region1.AssignAnyAddress(0, nil)
|
||||
rs.region1.AssignAnyAddress(1, nil)
|
||||
rs.region2.AssignAnyAddress(2, nil)
|
||||
addr := rs.region2.active.GetUnusedIP(nil)
|
||||
|
||||
rs.region1.Use(&addr0, 0)
|
||||
rs.region1.Use(&addr1, 1)
|
||||
rs.region2.Use(&addr2, 2)
|
||||
|
||||
assert.Equal(t, 1, rs.AvailableAddrs())
|
||||
assert.Equal(t, &addr3, rs.GetUnusedAddr(nil, 3))
|
||||
assert.Equal(t, 1, rs.AvailableAddrs())
|
||||
assert.Equal(t, addr, rs.GetUnusedAddr(nil, 3))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegions_GetUnusedAddr_Excluding_Region1(t *testing.T) {
|
||||
rs := makeRegions()
|
||||
tests := []struct {
|
||||
name string
|
||||
addrs []*EdgeAddr
|
||||
mode ConfigIPVersion
|
||||
}{
|
||||
{
|
||||
name: "IPv4 addresses with IPv4Only",
|
||||
addrs: v4Addrs,
|
||||
mode: IPv4Only,
|
||||
},
|
||||
{
|
||||
name: "IPv6 addresses with IPv6Only",
|
||||
addrs: v6Addrs,
|
||||
mode: IPv6Only,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rs := makeRegions(tt.addrs, tt.mode)
|
||||
|
||||
rs.region1.Use(&addr0, 0)
|
||||
rs.region1.Use(&addr1, 1)
|
||||
rs.region1.AssignAnyAddress(0, nil)
|
||||
rs.region1.AssignAnyAddress(1, nil)
|
||||
addr := rs.region2.active.GetUnusedIP(nil)
|
||||
a2 := rs.region2.active.GetUnusedIP(addr)
|
||||
|
||||
assert.Equal(t, 2, rs.AvailableAddrs())
|
||||
assert.Equal(t, &addr3, rs.GetUnusedAddr(&addr2, 3))
|
||||
assert.Equal(t, 2, rs.AvailableAddrs())
|
||||
assert.Equal(t, addr, rs.GetUnusedAddr(a2, 3))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegions_GetUnusedAddr_Excluding_Region2(t *testing.T) {
|
||||
rs := makeRegions()
|
||||
tests := []struct {
|
||||
name string
|
||||
addrs []*EdgeAddr
|
||||
mode ConfigIPVersion
|
||||
}{
|
||||
{
|
||||
name: "IPv4 addresses with IPv4Only",
|
||||
addrs: v4Addrs,
|
||||
mode: IPv4Only,
|
||||
},
|
||||
{
|
||||
name: "IPv6 addresses with IPv6Only",
|
||||
addrs: v6Addrs,
|
||||
mode: IPv6Only,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rs := makeRegions(tt.addrs, tt.mode)
|
||||
|
||||
rs.region2.Use(&addr2, 0)
|
||||
rs.region2.Use(&addr3, 1)
|
||||
rs.region2.AssignAnyAddress(0, nil)
|
||||
rs.region2.AssignAnyAddress(1, nil)
|
||||
addr := rs.region1.active.GetUnusedIP(nil)
|
||||
a2 := rs.region1.active.GetUnusedIP(addr)
|
||||
|
||||
assert.Equal(t, 2, rs.AvailableAddrs())
|
||||
assert.Equal(t, &addr1, rs.GetUnusedAddr(&addr0, 1))
|
||||
assert.Equal(t, 2, rs.AvailableAddrs())
|
||||
assert.Equal(t, addr, rs.GetUnusedAddr(a2, 1))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewNoResolveBalancesRegions(t *testing.T) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package edgediscovery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
@@ -11,9 +10,16 @@ import (
|
||||
|
||||
const (
|
||||
LogFieldConnIndex = "connIndex"
|
||||
LogFieldIPAddress = "ip"
|
||||
)
|
||||
|
||||
var errNoAddressesLeft = fmt.Errorf("there are no free edge addresses left")
|
||||
var errNoAddressesLeft = ErrNoAddressesLeft{}
|
||||
|
||||
type ErrNoAddressesLeft struct{}
|
||||
|
||||
func (e ErrNoAddressesLeft) Error() string {
|
||||
return "there are no free edge addresses left to resolve to"
|
||||
}
|
||||
|
||||
// Edge finds addresses on the Cloudflare edge and hands them out to connections.
|
||||
type Edge struct {
|
||||
@@ -28,8 +34,8 @@ type Edge struct {
|
||||
|
||||
// ResolveEdge runs the initial discovery of the Cloudflare edge, finding Addrs that can be allocated
|
||||
// to connections.
|
||||
func ResolveEdge(log *zerolog.Logger, region string) (*Edge, error) {
|
||||
regions, err := allregions.ResolveEdge(log, region)
|
||||
func ResolveEdge(log *zerolog.Logger, region string, edgeIpVersion allregions.ConfigIPVersion) (*Edge, error) {
|
||||
regions, err := allregions.ResolveEdge(log, region, edgeIpVersion)
|
||||
if err != nil {
|
||||
return new(Edge), err
|
||||
}
|
||||
@@ -51,15 +57,6 @@ func StaticEdge(log *zerolog.Logger, hostnames []string) (*Edge, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MockEdge creates a Cloudflare Edge from arbitrary TCP addresses. Used for testing.
|
||||
func MockEdge(log *zerolog.Logger, addrs []*allregions.EdgeAddr) *Edge {
|
||||
regions := allregions.NewNoResolve(addrs)
|
||||
return &Edge{
|
||||
log: log,
|
||||
regions: regions,
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// Methods
|
||||
// ------------------------------------
|
||||
@@ -93,12 +90,15 @@ func (ed *Edge) GetAddr(connIndex int) (*allregions.EdgeAddr, error) {
|
||||
log.Debug().Msg("edgediscovery - GetAddr: No addresses left to give proxy connection")
|
||||
return nil, errNoAddressesLeft
|
||||
}
|
||||
log.Debug().Msg("edgediscovery - GetAddr: Giving connection its new address")
|
||||
log = ed.log.With().
|
||||
Int(LogFieldConnIndex, connIndex).
|
||||
IPAddr(LogFieldIPAddress, addr.UDP.IP).Logger()
|
||||
log.Debug().Msgf("edgediscovery - GetAddr: Giving connection its new address")
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// GetDifferentAddr gives back the proxy connection's edge Addr and uses a new one.
|
||||
func (ed *Edge) GetDifferentAddr(connIndex int) (*allregions.EdgeAddr, error) {
|
||||
func (ed *Edge) GetDifferentAddr(connIndex int, hasConnectivityError bool) (*allregions.EdgeAddr, error) {
|
||||
log := ed.log.With().Int(LogFieldConnIndex, connIndex).Logger()
|
||||
|
||||
ed.Lock()
|
||||
@@ -106,7 +106,7 @@ func (ed *Edge) GetDifferentAddr(connIndex int) (*allregions.EdgeAddr, error) {
|
||||
|
||||
oldAddr := ed.regions.AddrUsedBy(connIndex)
|
||||
if oldAddr != nil {
|
||||
ed.regions.GiveBack(oldAddr)
|
||||
ed.regions.GiveBack(oldAddr, hasConnectivityError)
|
||||
}
|
||||
addr := ed.regions.GetUnusedAddr(oldAddr, connIndex)
|
||||
if addr == nil {
|
||||
@@ -114,8 +114,10 @@ func (ed *Edge) GetDifferentAddr(connIndex int) (*allregions.EdgeAddr, error) {
|
||||
// note: if oldAddr were not nil, it will become available on the next iteration
|
||||
return nil, errNoAddressesLeft
|
||||
}
|
||||
log.Debug().Msgf("edgediscovery - GetDifferentAddr: Giving connection its new address: %v from the address list: %v",
|
||||
addr, ed.regions.AvailableAddrs())
|
||||
log = ed.log.With().
|
||||
Int(LogFieldConnIndex, connIndex).
|
||||
IPAddr(LogFieldIPAddress, addr.UDP.IP).Logger()
|
||||
log.Debug().Msgf("edgediscovery - GetDifferentAddr: Giving connection its new address from the address list: %v", ed.regions.AvailableAddrs())
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
@@ -128,9 +130,11 @@ func (ed *Edge) AvailableAddrs() int {
|
||||
|
||||
// GiveBack the address so that other connections can use it.
|
||||
// Returns true if the address is in this edge.
|
||||
func (ed *Edge) GiveBack(addr *allregions.EdgeAddr) bool {
|
||||
func (ed *Edge) GiveBack(addr *allregions.EdgeAddr, hasConnectivityError bool) bool {
|
||||
ed.Lock()
|
||||
defer ed.Unlock()
|
||||
ed.log.Debug().Msg("edgediscovery - GiveBack: Address now unused")
|
||||
return ed.regions.GiveBack(addr)
|
||||
log := ed.log.With().
|
||||
IPAddr(LogFieldIPAddress, addr.UDP.IP).Logger()
|
||||
log.Debug().Msgf("edgediscovery - GiveBack: Address now unused")
|
||||
return ed.regions.GiveBack(addr, hasConnectivityError)
|
||||
}
|
||||
|
||||
@@ -11,56 +11,113 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
addr0 = allregions.EdgeAddr{
|
||||
testLogger = zerolog.Nop()
|
||||
v4Addrs = []*allregions.EdgeAddr{&addr0, &addr1, &addr2, &addr3}
|
||||
v6Addrs = []*allregions.EdgeAddr{&addr4, &addr5, &addr6, &addr7}
|
||||
addr0 = allregions.EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("123.0.0.0"),
|
||||
IP: net.ParseIP("123.4.5.0"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("123.0.0.0"),
|
||||
IP: net.ParseIP("123.4.5.0"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: allregions.V4,
|
||||
}
|
||||
addr1 = allregions.EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("123.0.0.1"),
|
||||
IP: net.ParseIP("123.4.5.1"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("123.0.0.1"),
|
||||
IP: net.ParseIP("123.4.5.1"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: allregions.V4,
|
||||
}
|
||||
addr2 = allregions.EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("123.0.0.2"),
|
||||
IP: net.ParseIP("123.4.5.2"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("123.0.0.2"),
|
||||
IP: net.ParseIP("123.4.5.2"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: allregions.V4,
|
||||
}
|
||||
addr3 = allregions.EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("123.0.0.3"),
|
||||
IP: net.ParseIP("123.4.5.3"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("123.0.0.3"),
|
||||
IP: net.ParseIP("123.4.5.3"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: allregions.V4,
|
||||
}
|
||||
addr4 = allregions.EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::1"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::1"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: allregions.V6,
|
||||
}
|
||||
addr5 = allregions.EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::2"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::2"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: allregions.V6,
|
||||
}
|
||||
addr6 = allregions.EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::3"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::3"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: allregions.V6,
|
||||
}
|
||||
addr7 = allregions.EdgeAddr{
|
||||
TCP: &net.TCPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::4"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
UDP: &net.UDPAddr{
|
||||
IP: net.ParseIP("2606:4700:a0::4"),
|
||||
Port: 8000,
|
||||
Zone: "",
|
||||
},
|
||||
IPVersion: allregions.V6,
|
||||
}
|
||||
|
||||
testLogger = zerolog.Nop()
|
||||
)
|
||||
|
||||
func TestGiveBack(t *testing.T) {
|
||||
@@ -75,7 +132,7 @@ func TestGiveBack(t *testing.T) {
|
||||
assert.Equal(t, 3, edge.AvailableAddrs())
|
||||
|
||||
// Get it back
|
||||
edge.GiveBack(addr)
|
||||
edge.GiveBack(addr, false)
|
||||
assert.Equal(t, 4, edge.AvailableAddrs())
|
||||
}
|
||||
|
||||
@@ -107,7 +164,7 @@ func TestGetAddrForRPC(t *testing.T) {
|
||||
assert.Equal(t, 4, edge.AvailableAddrs())
|
||||
|
||||
// Get it back
|
||||
edge.GiveBack(addr)
|
||||
edge.GiveBack(addr, false)
|
||||
assert.Equal(t, 4, edge.AvailableAddrs())
|
||||
}
|
||||
|
||||
@@ -122,13 +179,13 @@ func TestOnePerRegion(t *testing.T) {
|
||||
assert.NotNil(t, a1)
|
||||
|
||||
// if the first address is bad, get the second one
|
||||
a2, err := edge.GetDifferentAddr(connID)
|
||||
a2, err := edge.GetDifferentAddr(connID, false)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, a2)
|
||||
assert.NotEqual(t, a1, a2)
|
||||
|
||||
// now that second one is bad, get the first one again
|
||||
a3, err := edge.GetDifferentAddr(connID)
|
||||
a3, err := edge.GetDifferentAddr(connID, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, a1, a3)
|
||||
}
|
||||
@@ -144,11 +201,11 @@ func TestOnlyOneAddrLeft(t *testing.T) {
|
||||
assert.NotNil(t, addr)
|
||||
|
||||
// If that edge address is "bad", there's no alternative address.
|
||||
_, err = edge.GetDifferentAddr(connID)
|
||||
_, err = edge.GetDifferentAddr(connID, false)
|
||||
assert.Error(t, err)
|
||||
|
||||
// previously bad address should become available again on next iteration.
|
||||
addr, err = edge.GetDifferentAddr(connID)
|
||||
addr, err = edge.GetDifferentAddr(connID, false)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, addr)
|
||||
}
|
||||
@@ -190,8 +247,17 @@ func TestGetDifferentAddr(t *testing.T) {
|
||||
assert.Equal(t, 3, edge.AvailableAddrs())
|
||||
|
||||
// If the same connection requests another address, it should get the same one.
|
||||
addr2, err := edge.GetDifferentAddr(connID)
|
||||
addr2, err := edge.GetDifferentAddr(connID, false)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, addr, addr2)
|
||||
assert.Equal(t, 3, edge.AvailableAddrs())
|
||||
}
|
||||
|
||||
// MockEdge creates a Cloudflare Edge from arbitrary TCP addresses. Used for testing.
|
||||
func MockEdge(log *zerolog.Logger, addrs []*allregions.EdgeAddr) *Edge {
|
||||
regions := allregions.NewNoResolve(addrs)
|
||||
return &Edge{
|
||||
log: log,
|
||||
regions: regions,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,11 @@
|
||||
|
||||
package main
|
||||
|
||||
import _ "crypto/tls/fipsonly"
|
||||
import (
|
||||
_ "crypto/tls/fipsonly"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel"
|
||||
)
|
||||
|
||||
func init () {
|
||||
tunnel.FipsEnabled = true
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,12 +2,12 @@
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
OUTPUT=$(for d in $(go list -mod=vendor -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc) ; do goimports -format-only -local github.com/cloudflare/cloudflared -d $d ; done)
|
||||
OUTPUT=$(goimports -l -d -local github.com/cloudflare/cloudflared $(go list -mod=vendor -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc))
|
||||
|
||||
if [ -n "$OUTPUT" ] ; then
|
||||
PAGER=$(which colordiff || echo cat)
|
||||
echo
|
||||
echo "Code formatting issues found, use 'goimports -format-only -local github.com/cloudflare/cloudflared' to correct them"
|
||||
echo "Code formatting issues found, use 'make fmt' to correct them"
|
||||
echo
|
||||
echo "$OUTPUT" | $PAGER
|
||||
exit 1
|
||||
|
||||
Regular → Executable
+74
-21
@@ -1,29 +1,56 @@
|
||||
#!/usr/bin/python3
|
||||
"""
|
||||
Creates Github Releases Notes with content hashes
|
||||
Create Github Releases Notes with binary checksums from Workers KV
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import hashlib
|
||||
import glob
|
||||
import requests
|
||||
|
||||
from github import Github, GithubException, UnknownObjectException
|
||||
from github import Github, UnknownObjectException
|
||||
|
||||
FORMAT = "%(levelname)s - %(asctime)s: %(message)s"
|
||||
logging.basicConfig(format=FORMAT)
|
||||
logging.basicConfig(format=FORMAT, level=logging.INFO)
|
||||
|
||||
CLOUDFLARED_REPO = os.environ.get("GITHUB_REPO", "cloudflare/cloudflared")
|
||||
GITHUB_CONFLICT_CODE = "already_exists"
|
||||
BASE_KV_URL = 'https://api.cloudflare.com/client/v4/accounts/'
|
||||
|
||||
def get_sha256(filename):
|
||||
""" get the sha256 of a file """
|
||||
sha256_hash = hashlib.sha256()
|
||||
with open(filename,"rb") as f:
|
||||
for byte_block in iter(lambda: f.read(4096),b""):
|
||||
sha256_hash.update(byte_block)
|
||||
return sha256_hash.hexdigest()
|
||||
|
||||
def kv_get_keys(prefix, account, namespace, api_token):
|
||||
""" get the KV keys for a given prefix """
|
||||
response = requests.get(
|
||||
BASE_KV_URL + account + "/storage/kv/namespaces/" +
|
||||
namespace + "/keys" + "?prefix=" + prefix,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer " + api_token,
|
||||
},
|
||||
)
|
||||
if response.status_code != 200:
|
||||
jsonResponse = response.json()
|
||||
errors = jsonResponse["errors"]
|
||||
if len(errors) > 0:
|
||||
raise Exception("failed to get checksums: {0}", errors[0])
|
||||
return response.json()["result"]
|
||||
|
||||
|
||||
def kv_get_value(key, account, namespace, api_token):
|
||||
""" get the KV value for a provided key """
|
||||
response = requests.get(
|
||||
BASE_KV_URL + account + "/storage/kv/namespaces/" + namespace + "/values/" + key,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer " + api_token,
|
||||
},
|
||||
)
|
||||
if response.status_code != 200:
|
||||
jsonResponse = response.json()
|
||||
errors = jsonResponse["errors"]
|
||||
if len(errors) > 0:
|
||||
raise Exception("failed to get checksums: {0}", errors[0])
|
||||
return response.text
|
||||
|
||||
|
||||
def update_or_add_message(msg, name, sha):
|
||||
@@ -42,6 +69,7 @@ def update_or_add_message(msg, name, sha):
|
||||
return '{0}{1}```'.format(msg[:back], new_text)
|
||||
return '{0} \n### SHA256 Checksums:\n```\n{1}```'.format(msg, new_text)
|
||||
|
||||
|
||||
def get_release(repo, version):
|
||||
""" Get a Github Release matching the version tag. """
|
||||
try:
|
||||
@@ -55,11 +83,20 @@ def get_release(repo, version):
|
||||
def parse_args():
|
||||
""" Parse and validate args """
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Creates Github Releases and uploads assets."
|
||||
description="Updates a Github Release with checksums from KV"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--api-key", default=os.environ.get("API_KEY"), help="Github API key"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--kv-namespace-id", default=os.environ.get("KV_NAMESPACE"), help="workers KV namespace id"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--kv-account-id", default=os.environ.get("KV_ACCOUNT"), help="workers KV account id"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--kv-api-token", default=os.environ.get("KV_API_TOKEN"), help="workers KV API Token"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--release-version",
|
||||
metavar="version",
|
||||
@@ -80,6 +117,18 @@ def parse_args():
|
||||
logging.error("Missing API key")
|
||||
is_valid = False
|
||||
|
||||
if not args.kv_namespace_id:
|
||||
logging.error("Missing KV namespace id")
|
||||
is_valid = False
|
||||
|
||||
if not args.kv_account_id:
|
||||
logging.error("Missing KV account id")
|
||||
is_valid = False
|
||||
|
||||
if not args.kv_api_token:
|
||||
logging.error("Missing KV API token")
|
||||
is_valid = False
|
||||
|
||||
if is_valid:
|
||||
return args
|
||||
|
||||
@@ -95,16 +144,20 @@ def main():
|
||||
repo = client.get_repo(CLOUDFLARED_REPO)
|
||||
release = get_release(repo, args.release_version)
|
||||
|
||||
msg = release.body
|
||||
msg = ""
|
||||
|
||||
for filepath in glob.glob("artifacts/*"):
|
||||
pkg_hash = get_sha256(filepath)
|
||||
# add the sha256 of the new artifact to the release message body
|
||||
name = os.path.basename(filepath)
|
||||
msg = update_or_add_message(msg, name, pkg_hash)
|
||||
prefix = f"update_{args.release_version}_"
|
||||
keys = kv_get_keys(prefix, args.kv_account_id,
|
||||
args.kv_namespace_id, args.kv_api_token)
|
||||
for key in [k["name"] for k in keys]:
|
||||
checksum = kv_get_value(
|
||||
key, args.kv_account_id, args.kv_namespace_id, args.kv_api_token)
|
||||
binary_name = key[len(prefix):]
|
||||
msg = update_or_add_message(msg, binary_name, checksum)
|
||||
|
||||
if args.dry_run:
|
||||
logging.info("Skipping asset upload because of dry-run")
|
||||
logging.info("Skipping release message update because of dry-run")
|
||||
logging.info(f"Github message:\n{msg}")
|
||||
return
|
||||
|
||||
# update the release body text
|
||||
@@ -115,4 +168,4 @@ def main():
|
||||
exit(1)
|
||||
|
||||
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
module github.com/cloudflare/cloudflared
|
||||
|
||||
go 1.17
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93
|
||||
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc
|
||||
github.com/coredns/coredns v1.8.7
|
||||
github.com/coredns/coredns v1.10.0
|
||||
github.com/coreos/go-oidc/v3 v3.4.0
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/gdamore/tcell v1.3.0
|
||||
github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10
|
||||
github.com/getsentry/raven-go v0.2.0
|
||||
github.com/getsentry/sentry-go v0.16.0
|
||||
github.com/gobwas/ws v1.0.4
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/lucas-clemente/quic-go v0.24.0
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
github.com/miekg/dns v1.1.45
|
||||
github.com/lucas-clemente/quic-go v0.28.1
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.12.1
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/rivo/tview v0.0.0-20200712113419-c65badfc3d92
|
||||
github.com/rs/zerolog v1.20.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
go.opentelemetry.io/contrib/propagators v0.22.0
|
||||
go.opentelemetry.io/otel v1.6.3
|
||||
@@ -35,26 +35,27 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.6.3
|
||||
go.opentelemetry.io/proto/otlp v0.15.0
|
||||
go.uber.org/automaxprocs v1.4.0
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
|
||||
google.golang.org/protobuf v1.28.0
|
||||
golang.org/x/crypto v0.5.0
|
||||
golang.org/x/net v0.5.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/sys v0.4.0
|
||||
golang.org/x/term v0.4.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/coreos/go-oidc.v2 v2.2.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
zombiezen.com/go/capnproto2 v2.18.0+incompatible
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/BurntSushi/toml v1.2.0 // indirect
|
||||
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect
|
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47 // indirect
|
||||
github.com/coredns/caddy v1.1.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
@@ -63,8 +64,6 @@ 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/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
@@ -73,40 +72,51 @@ require (
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.8 // indirect
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/gomega v1.23.0 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/rivo/uniseg v0.1.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/oauth2 v0.4.0 // indirect
|
||||
golang.org/x/text v0.6.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect
|
||||
google.golang.org/grpc v1.45.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd // indirect
|
||||
google.golang.org/grpc v1.51.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d
|
||||
|
||||
replace github.com/lucas-clemente/quic-go => github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62
|
||||
replace github.com/lucas-clemente/quic-go => github.com/chungthuang/quic-go v0.27.1-0.20220809135021-ca330f1dec9f
|
||||
|
||||
// Avoid 'CVE-2022-21698'
|
||||
replace github.com/prometheus/golang_client => github.com/prometheus/golang_client v1.12.1
|
||||
|
||||
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
// Post-quantum tunnel RTG-1339
|
||||
replace (
|
||||
// branch go1.18
|
||||
github.com/marten-seemann/qtls-go1-18 => github.com/cloudflare/qtls-pq v0.0.0-20230103171413-e7a2fb559a0e
|
||||
|
||||
// branch go1.19
|
||||
github.com/marten-seemann/qtls-go1-19 => github.com/cloudflare/qtls-pq v0.0.0-20230103171656-05e84f90909e
|
||||
)
|
||||
|
||||
@@ -28,14 +28,23 @@ cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y
|
||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
||||
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
|
||||
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
|
||||
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
|
||||
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
|
||||
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
|
||||
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
|
||||
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
@@ -45,44 +54,18 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/Azure/azure-sdk-for-go v61.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
|
||||
github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
|
||||
github.com/Azure/go-autorest/autorest v0.11.23/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.10/go.mod h1:zQXYYNX9kXzRMrJNVXWUfNy38oPMF5/2TeZ4Wylc9fE=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
|
||||
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
|
||||
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
|
||||
github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k=
|
||||
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -92,34 +75,39 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
|
||||
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/aws/aws-sdk-go v1.42.30/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
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/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/bwesterb/go-ristretto v1.2.2/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 h1:JLaf/iINcLyjwbtTsCJjc6rtlASgHeIJPrB6QmwURnA=
|
||||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s=
|
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62 h1:PLTB4iA6sOgAItzQY642tYdcGKfG/7i2gu93JQGgUcM=
|
||||
github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg=
|
||||
github.com/chungthuang/quic-go v0.27.1-0.20220809135021-ca330f1dec9f h1:UWC3XjwZzocdNCzzXxq9j/1SdHMZXhcTOsh/+gNRBUQ=
|
||||
github.com/chungthuang/quic-go v0.27.1-0.20220809135021-ca330f1dec9f/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93 h1:QrGfkZDnMxcWHaYDdB7CmqS9i26OAnUj/xcus/abYkY=
|
||||
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93/go.mod h1:QiTe66jFdP7cUKMCCf/WrvDyYdtdmdZfVcdoLbzaKVY=
|
||||
github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47 h1:YzpECHxZ9TzO7LpnKmPxItSd79lLgrR5heIlnqU4dTU=
|
||||
github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47/go.mod h1:qhx8gBILsYlbam7h09SvHDSkjpe3TfLA7b/z4rxJvkE=
|
||||
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc h1:Dvk3ySBsOm5EviLx6VCyILnafPcQinXGP5jbTdHUJgE=
|
||||
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc/go.mod h1:HlgKKR8V5a1wroIDDIz3/A+T+9Janfq+7n1P5sEFdi0=
|
||||
github.com/cloudflare/qtls-pq v0.0.0-20230103171413-e7a2fb559a0e h1:frfo+L0qloEb6Vj+qjS4pbAYSJQZAlUnKZu0uJoErac=
|
||||
github.com/cloudflare/qtls-pq v0.0.0-20230103171413-e7a2fb559a0e/go.mod h1:mW0BgKFFDAiSmOdUwoORtjo0V2vqw5QzVYRtKQqw/Jg=
|
||||
github.com/cloudflare/qtls-pq v0.0.0-20230103171656-05e84f90909e h1:RtQDXvDi0PK3EonP0v7zkE5/rApK4MsgRATCdD+ughg=
|
||||
github.com/cloudflare/qtls-pq v0.0.0-20230103171656-05e84f90909e/go.mod h1:aIsWqC0WXyUiUxBl/RfxAjDyWE9CCLqvSMnCMTd/+bc=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
@@ -127,17 +115,18 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
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.8.7 h1:wVMjAnyFnY7Mc18AFO+9qbGD6ODPtdVUIlzoWrHr3hk=
|
||||
github.com/coredns/coredns v1.8.7/go.mod h1:bFmbgEfeRz5aizL2VsQ5LRlsvJuXWkgG/MWG9zxqjVM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coredns/coredns v1.10.0 h1:jCfuWsBjTs0dapkkhISfPCzn5LqvSRtrFtaf/Tjj4DI=
|
||||
github.com/coredns/coredns v1.10.0/go.mod h1:CIfRU5TgpuoIiJBJ4XrofQzfFQpPFh32ERpUevrSlaw=
|
||||
github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g=
|
||||
github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
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=
|
||||
@@ -145,18 +134,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/dnstap/golang-dnstap v0.4.0/go.mod h1:FqsSdH58NAmkAvKcpyxht7i4FoBjKu8E4JUPt8ipSUs=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@@ -165,8 +143,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
|
||||
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 h1:wWke/RUCl7VRjQhwPlR/v0glZXNYzBHdNUzf/Am2Nmg=
|
||||
@@ -177,49 +155,36 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
||||
github.com/farsightsec/golang-framestream v0.3.0/go.mod h1:eNde4IQyEiA5br02AouhEHCu3p3UzrCdFR4LuQHklMI=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
|
||||
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
|
||||
github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
|
||||
github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10 h1:YO10pIIBftO/kkTFdWhctH96grJ7qiy7bMdiZcIvPKs=
|
||||
github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
|
||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/getsentry/sentry-go v0.16.0 h1:owk+S+5XcgJLlGR/3+3s6N4d+uKwqYvh/eS0AIMjPWo=
|
||||
github.com/getsentry/sentry-go v0.16.0/go.mod h1:ZXCloQLj0pG7mja5NK6NPf2V4A88YJ4pNlc2mOHwh6Y=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
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-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
@@ -229,21 +194,15 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
|
||||
github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
@@ -273,12 +232,9 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -291,13 +247,14 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.4/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.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
@@ -313,7 +270,6 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210423192551-a2663126120b/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
@@ -322,52 +278,35 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
|
||||
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
|
||||
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
|
||||
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
|
||||
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
|
||||
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/infobloxopen/go-trees v0.0.0-20200715205103-96a057b8dfb9/go.mod h1:BaIJzjD2ZnHmx2acPF6XfGLPzNCMiBbMRqJr+8/8uRI=
|
||||
github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d h1:PRDnysJ9dF1vUMmEzBu6aHQeUluSQy4eWH3RsSSy/vI=
|
||||
github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
|
||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@@ -378,14 +317,11 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -395,39 +331,24 @@ 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=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.0 h1:P9ggrs5xtwiqXv/FHNwntmuLMNq3KaSIG93AtAZ48xk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 h1:EnzzN9fPUkUck/1CuY1FlzBaIYMoiBsdwTNmNGkwUUM=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
|
||||
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk=
|
||||
github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -435,52 +356,34 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
||||
github.com/modern-go/reflect2 v1.0.1/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/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
|
||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
|
||||
github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
|
||||
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/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
|
||||
github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
|
||||
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
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/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||
@@ -490,8 +393,9 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
|
||||
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@@ -501,22 +405,17 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
||||
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rivo/tview v0.0.0-20200712113419-c65badfc3d92 h1:rqaqSUdaW+OBbjnsrOoiaJv43mSRARuvsAuirmdxu7E=
|
||||
github.com/rivo/tview v0.0.0-20200712113419-c65badfc3d92/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84=
|
||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
@@ -553,42 +452,32 @@ github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5k
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
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.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
|
||||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
@@ -613,11 +502,8 @@ go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5f
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v0.15.0 h1:h0bKrvdrT/9sBwEJ6iWUqT/N/xPcS66bL4u3isneJ6w=
|
||||
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
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/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -629,12 +515,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -670,8 +553,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -690,8 +574,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -719,11 +601,17 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs=
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -741,8 +629,14 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
|
||||
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
|
||||
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -754,8 +648,10 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -770,20 +666,15 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -812,7 +703,6 @@ golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -824,15 +714,29 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -841,13 +745,13 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -871,7 +775,6 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
@@ -886,11 +789,9 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
@@ -900,20 +801,22 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
@@ -947,7 +850,15 @@ google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv
|
||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
||||
google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM=
|
||||
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
|
||||
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
|
||||
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
|
||||
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
|
||||
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
|
||||
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
|
||||
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
|
||||
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
|
||||
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -993,7 +904,6 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
@@ -1002,6 +912,7 @@ google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
@@ -1021,16 +932,33 @@ google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEc
|
||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb h1:ZrsicilzPCS/Xr8qtBZZLpy4P9TYXAfl49ctG1/5tgw=
|
||||
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
||||
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd h1:OjndDrsik+Gt+e6fs45z9AxiewiKyLKYpA45W5Kpkks=
|
||||
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
@@ -1054,11 +982,14 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
|
||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
|
||||
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U=
|
||||
google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
@@ -1073,14 +1004,13 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.34.0/go.mod h1:HtrC65fyJ6lWazShCC9rlOeiTSZJ0XtZhkwjZM2WpC4=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/coreos/go-oidc.v2 v2.2.1 h1:MY5SZClJ7vhjKfr64a4nHAOV/c3WH2gB9BMrR64J1Mc=
|
||||
@@ -1099,14 +1029,11 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -1116,24 +1043,9 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo=
|
||||
k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno=
|
||||
k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0=
|
||||
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||
k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk=
|
||||
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
zombiezen.com/go/capnproto2 v2.18.0+incompatible h1:mwfXZniffG5mXokQGHUJWGnqIBggoPfT/CEwon9Yess=
|
||||
|
||||
+191
-20
@@ -12,10 +12,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
defaultConnectTimeout = config.CustomDuration{Duration: 30 * time.Second}
|
||||
defaultTLSTimeout = config.CustomDuration{Duration: 10 * time.Second}
|
||||
defaultTCPKeepAlive = config.CustomDuration{Duration: 30 * time.Second}
|
||||
defaultKeepAliveTimeout = config.CustomDuration{Duration: 90 * time.Second}
|
||||
defaultHTTPConnectTimeout = config.CustomDuration{Duration: 30 * time.Second}
|
||||
defaultWarpRoutingConnectTimeout = config.CustomDuration{Duration: 5 * time.Second}
|
||||
defaultTLSTimeout = config.CustomDuration{Duration: 10 * time.Second}
|
||||
defaultTCPKeepAlive = config.CustomDuration{Duration: 30 * time.Second}
|
||||
defaultKeepAliveTimeout = config.CustomDuration{Duration: 90 * time.Second}
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -35,47 +36,90 @@ const (
|
||||
NoChunkedEncodingFlag = "no-chunked-encoding"
|
||||
ProxyAddressFlag = "proxy-address"
|
||||
ProxyPortFlag = "proxy-port"
|
||||
Http2OriginFlag = "http2-origin"
|
||||
)
|
||||
|
||||
const (
|
||||
socksProxy = "socks"
|
||||
)
|
||||
|
||||
type WarpRoutingConfig struct {
|
||||
Enabled bool `yaml:"enabled" json:"enabled"`
|
||||
ConnectTimeout config.CustomDuration `yaml:"connectTimeout" json:"connectTimeout,omitempty"`
|
||||
TCPKeepAlive config.CustomDuration `yaml:"tcpKeepAlive" json:"tcpKeepAlive,omitempty"`
|
||||
}
|
||||
|
||||
func NewWarpRoutingConfig(raw *config.WarpRoutingConfig) WarpRoutingConfig {
|
||||
cfg := WarpRoutingConfig{
|
||||
Enabled: raw.Enabled,
|
||||
ConnectTimeout: defaultWarpRoutingConnectTimeout,
|
||||
TCPKeepAlive: defaultTCPKeepAlive,
|
||||
}
|
||||
if raw.ConnectTimeout != nil {
|
||||
cfg.ConnectTimeout = *raw.ConnectTimeout
|
||||
}
|
||||
if raw.TCPKeepAlive != nil {
|
||||
cfg.TCPKeepAlive = *raw.TCPKeepAlive
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (c *WarpRoutingConfig) RawConfig() config.WarpRoutingConfig {
|
||||
raw := config.WarpRoutingConfig{
|
||||
Enabled: c.Enabled,
|
||||
}
|
||||
if c.ConnectTimeout.Duration != defaultWarpRoutingConnectTimeout.Duration {
|
||||
raw.ConnectTimeout = &c.ConnectTimeout
|
||||
}
|
||||
if c.TCPKeepAlive.Duration != defaultTCPKeepAlive.Duration {
|
||||
raw.TCPKeepAlive = &c.TCPKeepAlive
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
// RemoteConfig models ingress settings that can be managed remotely, for example through the dashboard.
|
||||
type RemoteConfig struct {
|
||||
Ingress Ingress
|
||||
WarpRouting config.WarpRoutingConfig
|
||||
WarpRouting WarpRoutingConfig
|
||||
}
|
||||
|
||||
type remoteConfigJSON struct {
|
||||
GlobalOriginRequest config.OriginRequestConfig `json:"originRequest"`
|
||||
type RemoteConfigJSON struct {
|
||||
GlobalOriginRequest *config.OriginRequestConfig `json:"originRequest,omitempty"`
|
||||
IngressRules []config.UnvalidatedIngressRule `json:"ingress"`
|
||||
WarpRouting config.WarpRoutingConfig `json:"warp-routing"`
|
||||
}
|
||||
|
||||
func (rc *RemoteConfig) UnmarshalJSON(b []byte) error {
|
||||
var rawConfig remoteConfigJSON
|
||||
var rawConfig RemoteConfigJSON
|
||||
|
||||
if err := json.Unmarshal(b, &rawConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
ingress, err := validateIngress(rawConfig.IngressRules, originRequestFromConfig(rawConfig.GlobalOriginRequest))
|
||||
|
||||
// if nil, just assume the default values.
|
||||
globalOriginRequestConfig := rawConfig.GlobalOriginRequest
|
||||
if globalOriginRequestConfig == nil {
|
||||
globalOriginRequestConfig = &config.OriginRequestConfig{}
|
||||
}
|
||||
|
||||
ingress, err := validateIngress(rawConfig.IngressRules, originRequestFromConfig(*globalOriginRequestConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc.Ingress = ingress
|
||||
rc.WarpRouting = rawConfig.WarpRouting
|
||||
rc.WarpRouting = NewWarpRoutingConfig(&rawConfig.WarpRouting)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
||||
var connectTimeout config.CustomDuration = defaultConnectTimeout
|
||||
var tlsTimeout config.CustomDuration = defaultTLSTimeout
|
||||
var tcpKeepAlive config.CustomDuration = defaultTCPKeepAlive
|
||||
var connectTimeout = defaultHTTPConnectTimeout
|
||||
var tlsTimeout = defaultTLSTimeout
|
||||
var tcpKeepAlive = defaultTCPKeepAlive
|
||||
var noHappyEyeballs bool
|
||||
var keepAliveConnections int = defaultKeepAliveConnections
|
||||
var keepAliveTimeout config.CustomDuration = defaultKeepAliveTimeout
|
||||
var keepAliveConnections = defaultKeepAliveConnections
|
||||
var keepAliveTimeout = defaultKeepAliveTimeout
|
||||
var httpHostHeader string
|
||||
var originServerName string
|
||||
var caPool string
|
||||
@@ -85,6 +129,7 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
||||
var proxyAddress = defaultProxyAddress
|
||||
var proxyPort uint
|
||||
var proxyType string
|
||||
var http2Origin bool
|
||||
if flag := ProxyConnectTimeoutFlag; c.IsSet(flag) {
|
||||
connectTimeout = config.CustomDuration{Duration: c.Duration(flag)}
|
||||
}
|
||||
@@ -128,9 +173,13 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
||||
// Note TUN-3758 , we use Int because UInt is not supported with altsrc
|
||||
proxyPort = uint(c.Int(flag))
|
||||
}
|
||||
if flag := Http2OriginFlag; c.IsSet(flag) {
|
||||
http2Origin = c.Bool(flag)
|
||||
}
|
||||
if c.IsSet(Socks5Flag) {
|
||||
proxyType = socksProxy
|
||||
}
|
||||
|
||||
return OriginRequestConfig{
|
||||
ConnectTimeout: connectTimeout,
|
||||
TLSTimeout: tlsTimeout,
|
||||
@@ -147,12 +196,13 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
||||
ProxyAddress: proxyAddress,
|
||||
ProxyPort: proxyPort,
|
||||
ProxyType: proxyType,
|
||||
Http2Origin: http2Origin,
|
||||
}
|
||||
}
|
||||
|
||||
func originRequestFromConfig(c config.OriginRequestConfig) OriginRequestConfig {
|
||||
out := OriginRequestConfig{
|
||||
ConnectTimeout: defaultConnectTimeout,
|
||||
ConnectTimeout: defaultHTTPConnectTimeout,
|
||||
TLSTimeout: defaultTLSTimeout,
|
||||
TCPKeepAlive: defaultTCPKeepAlive,
|
||||
KeepAliveConnections: defaultKeepAliveConnections,
|
||||
@@ -212,6 +262,12 @@ func originRequestFromConfig(c config.OriginRequestConfig) OriginRequestConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.Http2Origin != nil {
|
||||
out.Http2Origin = *c.Http2Origin
|
||||
}
|
||||
if c.Access != nil {
|
||||
out.Access = *c.Access
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -255,6 +311,11 @@ type OriginRequestConfig struct {
|
||||
ProxyType string `yaml:"proxyType" json:"proxyType"`
|
||||
// IP rules for the proxy service
|
||||
IPRules []ipaccess.Rule `yaml:"ipRules" json:"ipRules"`
|
||||
// Attempt to connect to origin with HTTP/2
|
||||
Http2Origin bool `yaml:"http2Origin" json:"http2Origin"`
|
||||
|
||||
// Access holds all access related configs
|
||||
Access config.AccessConfig `yaml:"access" json:"access,omitempty"`
|
||||
}
|
||||
|
||||
func (defaults *OriginRequestConfig) setConnectTimeout(overrides config.OriginRequestConfig) {
|
||||
@@ -360,12 +421,25 @@ func (defaults *OriginRequestConfig) setIPRules(overrides config.OriginRequestCo
|
||||
}
|
||||
}
|
||||
|
||||
func (defaults *OriginRequestConfig) setHttp2Origin(overrides config.OriginRequestConfig) {
|
||||
if val := overrides.Http2Origin; val != nil {
|
||||
defaults.Http2Origin = *val
|
||||
}
|
||||
}
|
||||
|
||||
func (defaults *OriginRequestConfig) setAccess(overrides config.OriginRequestConfig) {
|
||||
if val := overrides.Access; val != nil {
|
||||
defaults.Access = *val
|
||||
}
|
||||
}
|
||||
|
||||
// SetConfig gets config for the requests that cloudflared sends to origins.
|
||||
// Each field has a setter method which sets a value for the field by trying to find:
|
||||
// 1. The user config for this rule
|
||||
// 2. The user config for the overall ingress config
|
||||
// 3. Defaults chosen by the cloudflared team
|
||||
// 4. Golang zero values for that type
|
||||
// 1. The user config for this rule
|
||||
// 2. The user config for the overall ingress config
|
||||
// 3. Defaults chosen by the cloudflared team
|
||||
// 4. Golang zero values for that type
|
||||
//
|
||||
// If an earlier option isn't set, it will try the next option down.
|
||||
func setConfig(defaults OriginRequestConfig, overrides config.OriginRequestConfig) OriginRequestConfig {
|
||||
cfg := defaults
|
||||
@@ -385,5 +459,102 @@ func setConfig(defaults OriginRequestConfig, overrides config.OriginRequestConfi
|
||||
cfg.setProxyAddress(overrides)
|
||||
cfg.setProxyType(overrides)
|
||||
cfg.setIPRules(overrides)
|
||||
cfg.setHttp2Origin(overrides)
|
||||
cfg.setAccess(overrides)
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func ConvertToRawOriginConfig(c OriginRequestConfig) config.OriginRequestConfig {
|
||||
var connectTimeout *config.CustomDuration
|
||||
var tlsTimeout *config.CustomDuration
|
||||
var tcpKeepAlive *config.CustomDuration
|
||||
var keepAliveConnections *int
|
||||
var keepAliveTimeout *config.CustomDuration
|
||||
var proxyAddress *string
|
||||
var access *config.AccessConfig
|
||||
|
||||
if c.ConnectTimeout != defaultHTTPConnectTimeout {
|
||||
connectTimeout = &c.ConnectTimeout
|
||||
}
|
||||
if c.TLSTimeout != defaultTLSTimeout {
|
||||
tlsTimeout = &c.TLSTimeout
|
||||
}
|
||||
if c.TCPKeepAlive != defaultTCPKeepAlive {
|
||||
tcpKeepAlive = &c.TCPKeepAlive
|
||||
}
|
||||
if c.KeepAliveConnections != defaultKeepAliveConnections {
|
||||
keepAliveConnections = &c.KeepAliveConnections
|
||||
}
|
||||
if c.KeepAliveTimeout != defaultKeepAliveTimeout {
|
||||
keepAliveTimeout = &c.KeepAliveTimeout
|
||||
}
|
||||
if c.ProxyAddress != defaultProxyAddress {
|
||||
proxyAddress = &c.ProxyAddress
|
||||
}
|
||||
if c.Access.Required {
|
||||
access = &c.Access
|
||||
}
|
||||
|
||||
return config.OriginRequestConfig{
|
||||
ConnectTimeout: connectTimeout,
|
||||
TLSTimeout: tlsTimeout,
|
||||
TCPKeepAlive: tcpKeepAlive,
|
||||
NoHappyEyeballs: defaultBoolToNil(c.NoHappyEyeballs),
|
||||
KeepAliveConnections: keepAliveConnections,
|
||||
KeepAliveTimeout: keepAliveTimeout,
|
||||
HTTPHostHeader: emptyStringToNil(c.HTTPHostHeader),
|
||||
OriginServerName: emptyStringToNil(c.OriginServerName),
|
||||
CAPool: emptyStringToNil(c.CAPool),
|
||||
NoTLSVerify: defaultBoolToNil(c.NoTLSVerify),
|
||||
DisableChunkedEncoding: defaultBoolToNil(c.DisableChunkedEncoding),
|
||||
BastionMode: defaultBoolToNil(c.BastionMode),
|
||||
ProxyAddress: proxyAddress,
|
||||
ProxyPort: zeroUIntToNil(c.ProxyPort),
|
||||
ProxyType: emptyStringToNil(c.ProxyType),
|
||||
IPRules: convertToRawIPRules(c.IPRules),
|
||||
Http2Origin: defaultBoolToNil(c.Http2Origin),
|
||||
Access: access,
|
||||
}
|
||||
}
|
||||
|
||||
func convertToRawIPRules(ipRules []ipaccess.Rule) []config.IngressIPRule {
|
||||
result := make([]config.IngressIPRule, 0)
|
||||
for _, r := range ipRules {
|
||||
cidr := r.StringCIDR()
|
||||
|
||||
newRule := config.IngressIPRule{
|
||||
Prefix: &cidr,
|
||||
Ports: r.Ports(),
|
||||
Allow: r.RulePolicy(),
|
||||
}
|
||||
|
||||
result = append(result, newRule)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func defaultBoolToNil(b bool) *bool {
|
||||
if b == false {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &b
|
||||
}
|
||||
|
||||
func emptyStringToNil(s string) *string {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func zeroUIntToNil(v uint) *uint {
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &v
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
@@ -274,7 +274,7 @@ func TestOriginRequestConfigDefaults(t *testing.T) {
|
||||
// Rule 0 didn't override anything, so it inherits the cloudflared defaults
|
||||
actual0 := ing.Rules[0].Config
|
||||
expected0 := OriginRequestConfig{
|
||||
ConnectTimeout: defaultConnectTimeout,
|
||||
ConnectTimeout: defaultHTTPConnectTimeout,
|
||||
TLSTimeout: defaultTLSTimeout,
|
||||
TCPKeepAlive: defaultTCPKeepAlive,
|
||||
KeepAliveConnections: defaultKeepAliveConnections,
|
||||
@@ -404,7 +404,7 @@ func TestDefaultConfigFromCLI(t *testing.T) {
|
||||
c := cli.NewContext(nil, set, nil)
|
||||
|
||||
expected := OriginRequestConfig{
|
||||
ConnectTimeout: defaultConnectTimeout,
|
||||
ConnectTimeout: defaultHTTPConnectTimeout,
|
||||
TLSTimeout: defaultTLSTimeout,
|
||||
TCPKeepAlive: defaultTCPKeepAlive,
|
||||
KeepAliveConnections: defaultKeepAliveConnections,
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
//go:build darwin
|
||||
|
||||
package ingress
|
||||
|
||||
// This file implements ICMPProxy for Darwin. It uses a non-privileged ICMP socket to send echo requests and listen for
|
||||
// echo replies. The source IP of the requests are rewritten to the bind IP of the socket and the socket reads all
|
||||
// messages, so we use echo ID to distinguish the replies. Each (source IP, destination IP, echo ID) is assigned a
|
||||
// unique echo ID.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"golang.org/x/net/icmp"
|
||||
|
||||
"github.com/cloudflare/cloudflared/packet"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
)
|
||||
|
||||
type icmpProxy struct {
|
||||
srcFunnelTracker *packet.FunnelTracker
|
||||
echoIDTracker *echoIDTracker
|
||||
conn *icmp.PacketConn
|
||||
// Response is handled in one-by-one, so encoder can be shared between funnels
|
||||
encoder *packet.Encoder
|
||||
logger *zerolog.Logger
|
||||
idleTimeout time.Duration
|
||||
}
|
||||
|
||||
// echoIDTracker tracks which ID has been assigned. It first loops through assignment from lastAssignment to then end,
|
||||
// then from the beginning to lastAssignment.
|
||||
// ICMP echo are short lived. By the time an ID is revisited, it should have been released.
|
||||
type echoIDTracker struct {
|
||||
lock sync.Mutex
|
||||
// maps the source IP, destination IP and original echo ID to a unique echo ID obtained from assignment
|
||||
mapping map[flow3Tuple]uint16
|
||||
// assignment tracks if an ID is assigned using index as the ID
|
||||
// The size of the array is math.MaxUint16 because echo ID is 2 bytes
|
||||
assignment [math.MaxUint16]bool
|
||||
// nextAssignment is the next number to check for assigment
|
||||
nextAssignment uint16
|
||||
}
|
||||
|
||||
func newEchoIDTracker() *echoIDTracker {
|
||||
return &echoIDTracker{
|
||||
mapping: make(map[flow3Tuple]uint16),
|
||||
}
|
||||
}
|
||||
|
||||
// Get assignment or assign a new ID.
|
||||
func (eit *echoIDTracker) getOrAssign(key flow3Tuple) (id uint16, success bool) {
|
||||
eit.lock.Lock()
|
||||
defer eit.lock.Unlock()
|
||||
id, exists := eit.mapping[key]
|
||||
if exists {
|
||||
return id, true
|
||||
}
|
||||
|
||||
if eit.nextAssignment == math.MaxUint16 {
|
||||
eit.nextAssignment = 0
|
||||
}
|
||||
|
||||
for i, assigned := range eit.assignment[eit.nextAssignment:] {
|
||||
if !assigned {
|
||||
echoID := uint16(i) + eit.nextAssignment
|
||||
eit.set(key, echoID)
|
||||
return echoID, true
|
||||
}
|
||||
}
|
||||
for i, assigned := range eit.assignment[0:eit.nextAssignment] {
|
||||
if !assigned {
|
||||
echoID := uint16(i)
|
||||
eit.set(key, echoID)
|
||||
return echoID, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Caller should hold the lock
|
||||
func (eit *echoIDTracker) set(key flow3Tuple, assignedEchoID uint16) {
|
||||
eit.assignment[assignedEchoID] = true
|
||||
eit.mapping[key] = assignedEchoID
|
||||
eit.nextAssignment = assignedEchoID + 1
|
||||
}
|
||||
|
||||
func (eit *echoIDTracker) release(key flow3Tuple, assigned uint16) bool {
|
||||
eit.lock.Lock()
|
||||
defer eit.lock.Unlock()
|
||||
|
||||
currentEchoID, exists := eit.mapping[key]
|
||||
if exists && assigned == currentEchoID {
|
||||
delete(eit.mapping, key)
|
||||
eit.assignment[assigned] = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type echoFunnelID uint16
|
||||
|
||||
func (snf echoFunnelID) Type() string {
|
||||
return "echoID"
|
||||
}
|
||||
|
||||
func (snf echoFunnelID) String() string {
|
||||
return strconv.FormatUint(uint64(snf), 10)
|
||||
}
|
||||
|
||||
func newICMPProxy(listenIP netip.Addr, zone string, logger *zerolog.Logger, idleTimeout time.Duration) (*icmpProxy, error) {
|
||||
conn, err := newICMPConn(listenIP, zone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Info().Msgf("Created ICMP proxy listening on %s", conn.LocalAddr())
|
||||
return &icmpProxy{
|
||||
srcFunnelTracker: packet.NewFunnelTracker(),
|
||||
echoIDTracker: newEchoIDTracker(),
|
||||
encoder: packet.NewEncoder(),
|
||||
conn: conn,
|
||||
logger: logger,
|
||||
idleTimeout: idleTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ip *icmpProxy) Request(ctx context.Context, pk *packet.ICMP, responder *packetResponder) error {
|
||||
ctx, span := responder.requestSpan(ctx, pk)
|
||||
defer responder.exportSpan()
|
||||
|
||||
originalEcho, err := getICMPEcho(pk.Message)
|
||||
if err != nil {
|
||||
tracing.EndWithErrorStatus(span, err)
|
||||
return err
|
||||
}
|
||||
span.SetAttributes(
|
||||
attribute.Int("originalEchoID", originalEcho.ID),
|
||||
attribute.Int("seq", originalEcho.Seq),
|
||||
)
|
||||
echoIDTrackerKey := flow3Tuple{
|
||||
srcIP: pk.Src,
|
||||
dstIP: pk.Dst,
|
||||
originalEchoID: originalEcho.ID,
|
||||
}
|
||||
assignedEchoID, success := ip.echoIDTracker.getOrAssign(echoIDTrackerKey)
|
||||
if !success {
|
||||
err := fmt.Errorf("failed to assign unique echo ID")
|
||||
tracing.EndWithErrorStatus(span, err)
|
||||
return err
|
||||
}
|
||||
span.SetAttributes(attribute.Int("assignedEchoID", int(assignedEchoID)))
|
||||
|
||||
shouldReplaceFunnelFunc := createShouldReplaceFunnelFunc(ip.logger, responder.datagramMuxer, pk, originalEcho.ID)
|
||||
newFunnelFunc := func() (packet.Funnel, error) {
|
||||
originalEcho, err := getICMPEcho(pk.Message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
closeCallback := func() error {
|
||||
ip.echoIDTracker.release(echoIDTrackerKey, assignedEchoID)
|
||||
return nil
|
||||
}
|
||||
icmpFlow := newICMPEchoFlow(pk.Src, closeCallback, ip.conn, responder, int(assignedEchoID), originalEcho.ID, ip.encoder)
|
||||
return icmpFlow, nil
|
||||
}
|
||||
funnelID := echoFunnelID(assignedEchoID)
|
||||
funnel, isNew, err := ip.srcFunnelTracker.GetOrRegister(funnelID, shouldReplaceFunnelFunc, newFunnelFunc)
|
||||
if err != nil {
|
||||
tracing.EndWithErrorStatus(span, err)
|
||||
return err
|
||||
}
|
||||
if isNew {
|
||||
span.SetAttributes(attribute.Bool("newFlow", true))
|
||||
ip.logger.Debug().
|
||||
Str("src", pk.Src.String()).
|
||||
Str("dst", pk.Dst.String()).
|
||||
Int("originalEchoID", originalEcho.ID).
|
||||
Int("assignedEchoID", int(assignedEchoID)).
|
||||
Msg("New flow")
|
||||
}
|
||||
icmpFlow, err := toICMPEchoFlow(funnel)
|
||||
if err != nil {
|
||||
tracing.EndWithErrorStatus(span, err)
|
||||
return err
|
||||
}
|
||||
err = icmpFlow.sendToDst(pk.Dst, pk.Message)
|
||||
if err != nil {
|
||||
tracing.EndWithErrorStatus(span, err)
|
||||
return err
|
||||
}
|
||||
tracing.End(span)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serve listens for responses to the requests until context is done
|
||||
func (ip *icmpProxy) Serve(ctx context.Context) error {
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
ip.conn.Close()
|
||||
}()
|
||||
go func() {
|
||||
ip.srcFunnelTracker.ScheduleCleanup(ctx, ip.idleTimeout)
|
||||
}()
|
||||
buf := make([]byte, mtu)
|
||||
icmpDecoder := packet.NewICMPDecoder()
|
||||
for {
|
||||
n, from, err := ip.conn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reply, err := parseReply(from, buf[:n])
|
||||
if err != nil {
|
||||
ip.logger.Debug().Err(err).Str("dst", from.String()).Msg("Failed to parse ICMP reply, continue to parse as full packet")
|
||||
// In unit test, we found out when the listener listens on 0.0.0.0, the socket reads the full packet after
|
||||
// the second reply
|
||||
if err := ip.handleFullPacket(ctx, icmpDecoder, buf[:n]); err != nil {
|
||||
ip.logger.Debug().Err(err).Str("dst", from.String()).Msg("Failed to parse ICMP reply as full packet")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !isEchoReply(reply.msg) {
|
||||
ip.logger.Debug().Str("dst", from.String()).Msgf("Drop ICMP %s from reply", reply.msg.Type)
|
||||
continue
|
||||
}
|
||||
if err := ip.sendReply(ctx, reply); err != nil {
|
||||
ip.logger.Debug().Err(err).Str("dst", from.String()).Msg("Failed to send ICMP reply")
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ip *icmpProxy) handleFullPacket(ctx context.Context, decoder *packet.ICMPDecoder, rawPacket []byte) error {
|
||||
icmpPacket, err := decoder.Decode(packet.RawPacket{Data: rawPacket})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
echo, err := getICMPEcho(icmpPacket.Message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reply := echoReply{
|
||||
from: icmpPacket.Src,
|
||||
msg: icmpPacket.Message,
|
||||
echo: echo,
|
||||
}
|
||||
if ip.sendReply(ctx, &reply); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ip *icmpProxy) sendReply(ctx context.Context, reply *echoReply) error {
|
||||
funnelID := echoFunnelID(reply.echo.ID)
|
||||
funnel, ok := ip.srcFunnelTracker.Get(funnelID)
|
||||
if !ok {
|
||||
return packet.ErrFunnelNotFound
|
||||
}
|
||||
icmpFlow, err := toICMPEchoFlow(funnel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, span := icmpFlow.responder.replySpan(ctx, ip.logger)
|
||||
defer icmpFlow.responder.exportSpan()
|
||||
|
||||
span.SetAttributes(
|
||||
attribute.String("dst", reply.from.String()),
|
||||
attribute.Int("echoID", reply.echo.ID),
|
||||
attribute.Int("seq", reply.echo.Seq),
|
||||
attribute.Int("originalEchoID", icmpFlow.originalEchoID),
|
||||
)
|
||||
if err := icmpFlow.returnToSrc(reply); err != nil {
|
||||
tracing.EndWithErrorStatus(span, err)
|
||||
}
|
||||
tracing.End(span)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
//go:build darwin
|
||||
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cloudflare/cloudflared/packet"
|
||||
)
|
||||
|
||||
func TestSingleEchoIDTracker(t *testing.T) {
|
||||
tracker := newEchoIDTracker()
|
||||
key := flow3Tuple{
|
||||
srcIP: netip.MustParseAddr("172.16.0.1"),
|
||||
dstIP: netip.MustParseAddr("172.16.0.2"),
|
||||
originalEchoID: 5182,
|
||||
}
|
||||
|
||||
// not assigned yet, so nothing to release
|
||||
require.False(t, tracker.release(key, 0))
|
||||
|
||||
echoID, ok := tracker.getOrAssign(key)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, uint16(0), echoID)
|
||||
|
||||
// Second time should return the same echo ID
|
||||
echoID, ok = tracker.getOrAssign(key)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, uint16(0), echoID)
|
||||
|
||||
// releasing a different ID returns false
|
||||
require.False(t, tracker.release(key, 1999))
|
||||
require.True(t, tracker.release(key, echoID))
|
||||
// releasing the second time returns false
|
||||
require.False(t, tracker.release(key, echoID))
|
||||
|
||||
// Move to the next IP
|
||||
echoID, ok = tracker.getOrAssign(key)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, uint16(1), echoID)
|
||||
}
|
||||
|
||||
func TestFullEchoIDTracker(t *testing.T) {
|
||||
var (
|
||||
dstIP = netip.MustParseAddr("192.168.0.1")
|
||||
originalEchoID = 41820
|
||||
)
|
||||
tracker := newEchoIDTracker()
|
||||
firstSrcIP := netip.MustParseAddr("172.16.0.1")
|
||||
srcIP := firstSrcIP
|
||||
|
||||
for i := uint16(0); i < math.MaxUint16; i++ {
|
||||
key := flow3Tuple{
|
||||
srcIP: srcIP,
|
||||
dstIP: dstIP,
|
||||
originalEchoID: originalEchoID,
|
||||
}
|
||||
echoID, ok := tracker.getOrAssign(key)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, i, echoID)
|
||||
|
||||
echoID, ok = tracker.get(key)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, i, echoID)
|
||||
|
||||
srcIP = srcIP.Next()
|
||||
}
|
||||
|
||||
key := flow3Tuple{
|
||||
srcIP: srcIP.Next(),
|
||||
dstIP: dstIP,
|
||||
originalEchoID: originalEchoID,
|
||||
}
|
||||
// All echo IDs are assigned
|
||||
echoID, ok := tracker.getOrAssign(key)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, uint16(0), echoID)
|
||||
|
||||
srcIP = firstSrcIP
|
||||
for i := uint16(0); i < math.MaxUint16; i++ {
|
||||
key := flow3Tuple{
|
||||
srcIP: srcIP,
|
||||
dstIP: dstIP,
|
||||
originalEchoID: originalEchoID,
|
||||
}
|
||||
ok := tracker.release(key, i)
|
||||
require.True(t, ok)
|
||||
|
||||
echoID, ok = tracker.get(key)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, uint16(0), echoID)
|
||||
srcIP = srcIP.Next()
|
||||
}
|
||||
|
||||
// The IDs are assignable again
|
||||
srcIP = firstSrcIP
|
||||
for i := uint16(0); i < math.MaxUint16; i++ {
|
||||
key := flow3Tuple{
|
||||
srcIP: srcIP,
|
||||
dstIP: dstIP,
|
||||
originalEchoID: originalEchoID,
|
||||
}
|
||||
echoID, ok := tracker.getOrAssign(key)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, i, echoID)
|
||||
|
||||
echoID, ok = tracker.get(key)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, i, echoID)
|
||||
srcIP = srcIP.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (eit *echoIDTracker) get(key flow3Tuple) (id uint16, exist bool) {
|
||||
eit.lock.Lock()
|
||||
defer eit.lock.Unlock()
|
||||
id, exists := eit.mapping[key]
|
||||
return id, exists
|
||||
}
|
||||
|
||||
func getFunnel(t *testing.T, proxy *icmpProxy, tuple flow3Tuple) (packet.Funnel, bool) {
|
||||
assignedEchoID, success := proxy.echoIDTracker.getOrAssign(tuple)
|
||||
require.True(t, success)
|
||||
return proxy.srcFunnelTracker.Get(echoFunnelID(assignedEchoID))
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user