Compare commits

...

22 Commits

Author SHA1 Message Date
Sudarsan Reddy bcc847963e Release 2021.8.0 2021-08-02 09:28:51 +01:00
Nuno Diegues 1e8dea9112 TUN-4851: Component tests to sanity check that Proxy DNS and Tunnel are only run when expected 2021-08-02 08:01:09 +00:00
Sudarsan Reddy cd4af5696d TUN-4772: Release built executables with packages 2021-07-30 13:56:19 +00:00
Nuno Diegues 8527d03a29 TUN-4847: Allow to list tunnels by prefix name or exclusion prefix name 2021-07-30 12:00:26 +01:00
Nuno Diegues fa8aa02270 TUN-4819: Tolerate protocol TXT record lookup failing 2021-07-29 11:44:39 +00:00
Nuno Diegues aa24338225 TUN-4832: Prevent tunnel from running accidentally when only proxy-dns should run 2021-07-29 11:05:12 +00:00
Nuno Diegues 0924549efd TUN-4811: Publish quick tunnels' hostname in /metrics under userHostname for backwards-compatibility 2021-07-29 10:20:43 +01:00
Sudarsan Reddy 67a3be5b7a Release 2021.7.4 2021-07-28 10:35:17 +01:00
Sudarsan Reddy c33a516663 TUN-4812: Disable CGO for cloudflared builds 2021-07-28 09:33:59 +00:00
Sudarsan Reddy ed1389ef08 TUN-4814: Revert "TUN-4699: Make quick tunnels the default in cloudflared"
This reverts commit 18992efa0c.
2021-07-28 10:02:55 +01:00
Sudarsan Reddy 8fb6508ae6 Release 2021.7.3 2021-07-27 18:00:03 +01:00
Sudarsan Reddy ee8c8bd4c6 TUN-4799: Build deb, msi and rpm packages with fips 2021-07-27 11:38:22 +01:00
Sudarsan Reddy 0269062b24 Release 2021.7.2 2021-07-27 10:01:39 +01:00
Sudarsan Reddy bccf650165 Fixed a syntax error with python logging. 2021-07-27 09:33:41 +01:00
Sudarsan Reddy cecc74d3fb Release 2021.7.1 2021-07-26 17:19:43 +01:00
Rishabh Bector 18992efa0c TUN-4699: Make quick tunnels the default in cloudflared 2021-07-26 15:57:36 +00:00
Sudarsan Reddy bd8af7d80d TUN-4771: Upload deb, rpm and msi packages to github
- cfsetup now has a build command `github-release-pkgs` to release linux
   and msi packages to github.
 - github_message.py now has an option to upload all assets in a provided
   directory.
2021-07-26 13:49:17 +01:00
Sudarsan Reddy dff694b218 TUN-4761: Added a build-all-packages target to cfsetup 2021-07-22 16:36:49 +01:00
Sudarsan Reddy 38af26e232 TUN-4755: Add a windows msi release option to Make 2021-07-21 10:31:16 +01:00
Sudarsan Reddy 8f3526289a TUN-4701: Split Proxy into ProxyHTTP and ProxyTCP
http.Request now is only used by ProxyHTTP and not required if the
proxying is TCP. The dest conversion is handled by the transport layer.
2021-07-19 13:43:59 +00:00
Sudarsan Reddy 81dff44bb9 TUN-4596: Add QUIC application protocol for QUIC stream handshake
- Vendored the capnproto library to cloudflared.
- Added capnproto schema defining application protocol.
- Added Pogs and application level read write of the protocol.
2021-07-15 17:35:25 +00:00
cthuang 6e45e0d53b TUN-4714: Name nightly package cloudflared-nightly to avoid apt conflict 2021-07-14 20:45:29 +01:00
35 changed files with 1418 additions and 286 deletions
+1
View File
@@ -15,3 +15,4 @@ cscope.*
*-session.log
ssh_server_tests/.env
/.cover
built_artifacts/
+29 -9
View File
@@ -11,6 +11,13 @@ ifneq ($(GO_BUILD_TAGS),)
GO_BUILD_TAGS := -tags $(GO_BUILD_TAGS)
endif
ifeq ($(NIGHTLY), true)
DEB_PACKAGE_NAME := cloudflared-nightly
NIGHTLY_FLAGS := --conflicts cloudflared --replaces cloudflared
else
DEB_PACKAGE_NAME := cloudflared
endif
DATE := $(shell date -u '+%Y-%m-%d-%H%M UTC')
VERSION_FLAGS := -ldflags='-X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"'
@@ -81,7 +88,7 @@ clean:
go clean
.PHONY: cloudflared
cloudflared: tunnel-deps
cloudflared:
ifeq ($(FIPS), true)
$(info Building cloudflared with go-fips)
-test -f fips/fips.go && mv fips/fips.go fips/fips.go.linux-amd64
@@ -138,7 +145,7 @@ define build_package
--license 'Cloudflare Service Agreement' \
--url 'https://github.com/cloudflare/cloudflared' \
-m 'Cloudflare <support@cloudflare.com>' \
-a $(TARGET_ARCH) -v $(VERSION) -n cloudflared --after-install postinst.sh --after-remove postrm.sh \
-a $(TARGET_ARCH) -v $(VERSION) -n $(DEB_PACKAGE_NAME) $(NIGHTLY_FLAGS) --after-install postinst.sh --after-remove postrm.sh \
cloudflared=$(INSTALL_BINDIR) cloudflared.1=$(MAN_DIR)
endef
@@ -150,6 +157,14 @@ cloudflared-deb: cloudflared
cloudflared-rpm: cloudflared
$(call build_package,rpm)
.PHONY: cloudflared-pkg
cloudflared-pkg: cloudflared
$(call build_package,osxpkg)
.PHONY: cloudflared-msi
cloudflared-msi: cloudflared
wixl --define Version=$(VERSION) --define Path=$(EXECUTABLE_PATH) --output cloudflared-$(VERSION)-$(TARGET_ARCH).msi cloudflared.wxs
.PHONY: cloudflared-darwin-amd64.tgz
cloudflared-darwin-amd64.tgz: cloudflared
tar czf cloudflared-darwin-amd64.tgz cloudflared
@@ -224,6 +239,10 @@ release: bin/equinox
github-release: cloudflared
python3 github_release.py --path $(EXECUTABLE_PATH) --release-version $(VERSION)
.PHONY: github-release-built-pkgs
github-release-built-pkgs:
python3 github_release.py --path $(PWD)/built_artifacts --release-version $(VERSION)
.PHONY: github-message
github-message:
python3 github_message.py --release-version $(VERSION)
@@ -237,24 +256,25 @@ bin/equinox:
mkdir -p bin
curl -s https://bin.equinox.io/c/75JtLRTsJ3n/release-tool-beta-$(EQUINOX_PLATFORM).tgz | tar xz -C bin/
.PHONY: tunnel-deps
tunnel-deps: tunnelrpc/tunnelrpc.capnp.go
tunnelrpc/tunnelrpc.capnp.go: tunnelrpc/tunnelrpc.capnp
.PHONY: tunnelrpc-deps
tunnelrpc-deps:
which capnp # https://capnproto.org/install.html
which capnpc-go # go get zombiezen.com/go/capnproto2/capnpc-go
capnp compile -ogo tunnelrpc/tunnelrpc.capnp
.PHONY: quic-deps
quic-deps:
which capnp
which capnpc-go
capnp compile -ogo quic/schema/quic_metadata_protocol.capnp
.PHONY: vet
vet:
go vet -mod=vendor ./...
which go-sumtype # go get github.com/BurntSushi/go-sumtype (don't do this in build directory or this will cause vendor issues)
go-sumtype $$(go list -mod=vendor ./...)
.PHONY: msi
msi: cloudflared
go-msi make --msi cloudflared.msi --version $(MSI_VERSION)
.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
+27
View File
@@ -1,3 +1,30 @@
2021.8.0
- 2021-07-30 TUN-4847: Allow to list tunnels by prefix name or exclusion prefix name
- 2021-07-30 TUN-4772: Release built executables with packages
- 2021-07-30 TUN-4851: Component tests to sanity check that Proxy DNS and Tunnel are only run when expected
- 2021-07-28 TUN-4811: Publish quick tunnels' hostname in /metrics under `userHostname` for backwards-compatibility
- 2021-07-29 TUN-4832: Prevent tunnel from running accidentally when only proxy-dns should run
- 2021-07-28 TUN-4819: Tolerate protocol TXT record lookup failing
2021.7.4
- 2021-07-28 TUN-4814: Revert "TUN-4699: Make quick tunnels the default in cloudflared"
- 2021-07-28 TUN-4812: Disable CGO for cloudflared builds
2021.7.3
- 2021-07-27 TUN-4799: Build deb, msi and rpm packages with fips
2021.7.2
- 2021-07-27 Fixed a syntax error with python logging.
2021.7.1
- 2021-07-21 TUN-4755: Add a windows msi release option to Make
- 2021-07-22 TUN-4761: Added a build-all-packages target to cfsetup
- 2021-07-26 TUN-4771: Upload deb, rpm and msi packages to github
- 2021-07-14 TUN-4714: Name nightly package cloudflared-nightly to avoid apt conflict
- 2021-07-16 TUN-4701: Split Proxy into ProxyHTTP and ProxyTCP
- 2021-07-08 TUN-4596: Add QUIC application protocol for QUIC stream handshake
- 2021-07-09 TUN-4699: Make quick tunnels the default in cloudflared
2021.7.0
- 2021-07-01 TUN-4626: Proxy non-stream based origin websockets with http Roundtrip.
- 2021-07-01 TUN-4655: ingress.StreamBasedProxy.EstablishConnection takes dest input
+37
View File
@@ -0,0 +1,37 @@
VERSION=$(git describe --tags --always --dirty="-dev" --match "[0-9][0-9][0-9][0-9].*.*")
echo $VERSION
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
export FIPS=true
linuxArchs=("amd64" "386" "arm")
export TARGET_OS=linux
for arch in ${linuxArchs[@]}; do
export TARGET_ARCH=$arch
make cloudflared-deb
mv cloudflared\_$VERSION\_$arch.deb $ARTIFACT_DIR/cloudflared-linux-$arch.deb
# rpm packages invert the - and _ and use x86_64 instead of amd64.
RPMVERSION=$(echo $VERSION|sed -r 's/-/_/g')
RPMARCH=$arch
if [ $arch == "amd64" ];then
RPMARCH="x86_64"
fi
make cloudflared-rpm
mv cloudflared-$RPMVERSION-1.$RPMARCH.rpm $ARTIFACT_DIR/cloudflared-linux-$RPMARCH.rpm
# finally move the linux binary as well.
mv ./cloudflared $ARTIFACT_DIR/cloudflared-linux-$arch
done
+56 -87
View File
@@ -14,25 +14,73 @@ stretch: &stretch
- export GOARCH=amd64
- export FIPS=true
- make cloudflared
build-deb:
build-all-packages: #except osxpkg
build_dir: *build_dir
builddeps:
- *pinned_go_fips
- build-essential
- fakeroot
- rubygem-fpm
- rpm
- wget
# libmsi and libgcab are libraries the wixl binary depends on.
- libmsi-dev
- libgcab-dev
pre-cache:
# TODO: https://jira.cfops.it/browse/TUN-4792 Replace this wixl with the official one once msitools supports
# environment.
- wget https://github.com/sudarshan-reddy/msitools/releases/download/v0.101b/wixl -P /usr/local/bin
- chmod a+x /usr/local/bin/wixl
post-cache:
- export FIPS=true
- ./build-packages.sh
github-release-pkgs:
build_dir: *build_dir
builddeps:
- *pinned_go_fips
- build-essential
- fakeroot
- rubygem-fpm
- rpm
- wget
# libmsi and libgcab are libraries the wixl binary depends on.
- libmsi-dev
- libgcab-dev
- python3-setuptools
- python3-pip
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 pygithub
post-cache:
# build all packages 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
build-deb:
build_dir: *build_dir
builddeps: &build_deb_deps
- *pinned_go_fips
- build-essential
- fakeroot
- rubygem-fpm
post-cache:
- export GOOS=linux
- export GOARCH=amd64
- export FIPS=true
- make cloudflared-deb
build-deb-nightly:
build_dir: *build_dir
builddeps: *build_deb_deps
post-cache:
- export GOOS=linux
- export GOARCH=amd64
- export FIPS=true
- export NIGHTLY=true
- make cloudflared-deb
build-deb-arm64:
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- fakeroot
- rubygem-fpm
builddeps: *build_deb_deps
post-cache:
- export GOOS=linux
- export GOARCH=arm64
@@ -60,20 +108,6 @@ stretch: &stretch
- export GOARCH=amd64
- export FIPS=true
- make release
github-release-linux-amd64:
build_dir: *build_dir
builddeps:
- *pinned_go_fips
- build-essential
- python3-setuptools
- python3-pip
pre-cache: &install_pygithub
- pip3 install pygithub
post-cache:
- export GOOS=linux
- export GOARCH=amd64
- export FIPS=true
- make github-release
release-linux-armv6:
build_dir: *build_dir
builddeps:
@@ -85,20 +119,6 @@ stretch: &stretch
- export GOARCH=arm
- export CC=arm-linux-gnueabihf-gcc
- make release
github-release-linux-armv6:
build_dir: *build_dir
builddeps:
- *pinned_go
- crossbuild-essential-armhf
- gcc-arm-linux-gnueabihf
- python3-setuptools
- python3-pip
pre-cache: *install_pygithub
post-cache:
- export GOOS=linux
- export GOARCH=arm
- export CC=arm-linux-gnueabihf-gcc
- make github-release
release-linux-386:
build_dir: *build_dir
builddeps:
@@ -108,18 +128,6 @@ stretch: &stretch
- export GOOS=linux
- export GOARCH=386
- make release
github-release-linux-386:
build_dir: *build_dir
builddeps:
- *pinned_go
- gcc-multilib
- python3-setuptools
- python3-pip
pre-cache: *install_pygithub
post-cache:
- export GOOS=linux
- export GOARCH=386
- make github-release
release-windows-amd64:
build_dir: *build_dir
builddeps:
@@ -130,19 +138,6 @@ stretch: &stretch
- export GOARCH=amd64
- export CC=x86_64-w64-mingw32-gcc
- make release
github-release-windows-amd64:
build_dir: *build_dir
builddeps:
- *pinned_go
- gcc-mingw-w64
- python3-setuptools
- python3-pip
pre-cache: *install_pygithub
post-cache:
- export GOOS=windows
- export GOARCH=amd64
- export CC=x86_64-w64-mingw32-gcc
- make github-release
release-windows-386:
build_dir: *build_dir
builddeps:
@@ -153,40 +148,14 @@ stretch: &stretch
- export GOARCH=386
- export CC=i686-w64-mingw32-gcc-win32
- make release
github-release-windows-386:
build_dir: *build_dir
builddeps:
- *pinned_go
- gcc-mingw-w64
- python3-setuptools
- python3-pip
pre-cache: *install_pygithub
post-cache:
- export GOOS=windows
- export GOARCH=386
- export CC=i686-w64-mingw32-gcc-win32
- make github-release
github-release-linux-arm64:
build_dir: *build_dir
builddeps:
- *pinned_go
- crossbuild-essential-armhf
- g++-aarch64-linux-gnu
- python3-setuptools
- python3-pip
pre-cache: *install_pygithub
post-cache:
- export GOOS=linux
- export GOARCH=arm64
- export CC=aarch64-linux-gnu-gcc
- make github-release
github-release-macos-amd64:
build_dir: *build_dir
builddeps:
- *pinned_go
- python3-setuptools
- python3-pip
pre-cache: *install_pygithub
pre-cache: &install_pygithub
- pip3 install pygithub
post-cache:
- make github-mac-upload
test:
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0"?>
<?if $(var.Platform)="x86"?>
<?define Program_Files="ProgramFilesFolder"?>
<?else?>
<?define Program_Files="ProgramFiles64Folder"?>
<?endif?>
<?ifndef var.Version?>
<?error Undefined Version variable?>
<?endif?>
<?ifndef var.Path?>
<?error Undefined Path variable?>
<?endif?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="35e5e858-9372-4449-bf73-1cd6f7267128"
UpgradeCode="23f90fdd-9328-47ea-ab52-5380855a4b12"
Name="cloudflared"
Version="$(var.Version)"
Manufacturer="cloudflare"
Language="1033">
<Package InstallerVersion="200" Compressed="yes" Comments="Windows Installer Package" InstallScope="perMachine"/>
<Media Id="1" Cabinet="product.cab" EmbedCab="yes"/>
<Upgrade Id="23f90fdd-9328-47ea-ab52-5380855a4b12">
<UpgradeVersion Minimum="$(var.Version)" OnlyDetect="yes" Property="NEWERVERSIONDETECTED"/>
<UpgradeVersion Minimum="2020.8.0" Maximum="$(var.Version)" IncludeMinimum="yes" IncludeMaximum="no"
Property="OLDERVERSIONBEINGUPGRADED"/>
</Upgrade>
<Condition Message="A newer version of this software is already installed.">NOT NEWERVERSIONDETECTED</Condition>
<Directory Id="TARGETDIR" Name="SourceDir">
<!--This specifies where the cloudflared.exe is moved to in the windows Operation System-->
<Directory Id="$(var.Program_Files)">
<Directory Id="INSTALLDIR" Name="cloudflared">
<Component Id="ApplicationFiles" Guid="35e5e858-9372-4449-bf73-1cd6f7267128">
<File Id="ApplicationFile0" Source="$(var.Path)"/>
</Component>
</Directory>
</Directory>
<Component Id="ENVS" Guid="6bb74449-d10d-4f4a-933e-6fc9fa006eae">
<!--Set the cloudflared bin location to the Path Environment Variable-->
<Environment Id="ENV0"
Name="PATH"
Value="[INSTALLDIR]."
Permanent="no"
Part="all"
Action="set"
System="yes" />
</Component>
</Directory>
<Feature Id='Complete' Level='1'>
<ComponentRef Id="ENVS"/>
<ComponentRef Id='ApplicationFiles' />
</Feature>
</Product>
</Wix>
+1 -1
View File
@@ -165,7 +165,7 @@ func TunnelCommand(c *cli.Context) error {
// Unauthenticated named tunnel on <random>.<quick-tunnels-service>.com
// For now, default to legacy setup unless quick-service is specified
if c.String("hostname") == "" && c.String("quick-service") != "" {
if !dnsProxyStandAlone(c, nil) && c.String("hostname") == "" && c.String("quick-service") != "" {
return RunQuickTunnel(sc)
}
+7 -1
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/google/uuid"
@@ -51,9 +52,14 @@ func RunQuickTunnel(sc *subcommandContext) error {
TunnelName: data.Result.Name,
}
url := data.Result.Hostname
if !strings.HasPrefix(url, "https://") {
url = "https://" + url
}
for _, line := range connection.AsciiBox([]string{
"Your Quick Tunnel has been created! Visit it at:",
data.Result.Hostname,
url,
}, 2) {
sc.log.Info().Msg(line)
}
+18
View File
@@ -49,6 +49,16 @@ var (
Aliases: []string{"n"},
Usage: "List tunnels with the given `NAME`",
}
listNamePrefixFlag = &cli.StringFlag{
Name: "name-prefix",
Aliases: []string{"np"},
Usage: "List tunnels that start with the give `NAME` prefix",
}
listExcludeNamePrefixFlag = &cli.StringFlag{
Name: "exclude-name-prefix",
Aliases: []string{"enp"},
Usage: "List tunnels whose `NAME` does not start with the given prefix",
}
listExistedAtFlag = &cli.TimestampFlag{
Name: "when",
Aliases: []string{"w"},
@@ -217,6 +227,8 @@ func buildListCommand() *cli.Command {
outputFormatFlag,
showDeletedFlag,
listNameFlag,
listNamePrefixFlag,
listExcludeNamePrefixFlag,
listExistedAtFlag,
listIDFlag,
showRecentlyDisconnected,
@@ -243,6 +255,12 @@ func listCommand(c *cli.Context) error {
if name := c.String("name"); name != "" {
filter.ByName(name)
}
if namePrefix := c.String("name-prefix"); namePrefix != "" {
filter.ByNamePrefix(namePrefix)
}
if excludePrefix := c.String("exclude-name-prefix"); excludePrefix != "" {
filter.ExcludeNameWithPrefix(excludePrefix)
}
if existedAt := c.Timestamp("time"); existedAt != nil {
filter.ByExistedAt(*existedAt)
}
+10 -1
View File
@@ -3,7 +3,7 @@ import copy
from dataclasses import dataclass, InitVar
from constants import METRICS_PORT
from constants import METRICS_PORT, PROXY_DNS_PORT
# frozen=True raises exception when assigning to fields. This emulates immutability
@@ -93,3 +93,12 @@ class ClassicTunnelConfig(ClassicTunnelBaseConfig):
def get_url(self):
return "https://" + self.hostname
@dataclass(frozen=True)
class ProxyDnsConfig(BaseConfig):
full_config = {
"port": PROXY_DNS_PORT,
"no-autoupdate": True,
}
+29 -14
View File
@@ -1,14 +1,21 @@
import os
from enum import Enum, auto
from time import sleep
import pytest
import yaml
from time import sleep
from config import NamedTunnelConfig, ClassicTunnelConfig
from constants import BACKOFF_SECS
from config import NamedTunnelConfig, ClassicTunnelConfig, ProxyDnsConfig
from constants import BACKOFF_SECS, PROXY_DNS_PORT
from util import LOGGER
class CfdModes(Enum):
NAMED = auto()
CLASSIC = auto()
PROXY_DNS = auto()
@pytest.fixture(scope="session")
def component_tests_config():
config_file = os.getenv("COMPONENT_TESTS_CONFIG")
@@ -19,22 +26,30 @@ def component_tests_config():
config = yaml.safe_load(stream)
LOGGER.info(f"component tests base config {config}")
def _component_tests_config(additional_config={}, named_tunnel=True):
def _component_tests_config(additional_config={}, cfd_mode=CfdModes.NAMED, run_proxy_dns=True):
if run_proxy_dns:
# Regression test for TUN-4177, running with proxy-dns should not prevent tunnels from running.
# So we run all tests with it.
additional_config["proxy-dns"] = True
additional_config["proxy-dns-port"] = PROXY_DNS_PORT
else:
additional_config.pop("proxy-dns", None)
additional_config.pop("proxy-dns-port", None)
# Regression test for TUN-4177, running with proxy-dns should not prevent tunnels from running
additional_config["proxy-dns"] = True
additional_config["proxy-dns-port"] = 9053
if named_tunnel:
if cfd_mode is CfdModes.NAMED:
return NamedTunnelConfig(additional_config=additional_config,
cloudflared_binary=config['cloudflared_binary'],
tunnel=config['tunnel'],
credentials_file=config['credentials_file'],
ingress=config['ingress'])
return ClassicTunnelConfig(
additional_config=additional_config, cloudflared_binary=config['cloudflared_binary'],
hostname=config['classic_hostname'], origincert=config['origincert'])
elif cfd_mode is CfdModes.CLASSIC:
return ClassicTunnelConfig(
additional_config=additional_config, cloudflared_binary=config['cloudflared_binary'],
hostname=config['classic_hostname'], origincert=config['origincert'])
elif cfd_mode is CfdModes.PROXY_DNS:
return ProxyDnsConfig(cloudflared_binary=config['cloudflared_binary'])
else:
raise Exception(f"Unknown cloudflared mode {cfd_mode}")
return _component_tests_config
+2
View File
@@ -1,3 +1,5 @@
METRICS_PORT = 51000
MAX_RETRIES = 5
BACKOFF_SECS = 7
PROXY_DNS_PORT = 9053
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env python
import socket
from time import sleep
import constants
from conftest import CfdModes
from util import start_cloudflared, wait_tunnel_ready, check_tunnel_not_connected
# Sanity checks that test that we only run Proxy DNS and Tunnel when we really expect them to be there.
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
expect_tunnel = False
if cfd_mode == CfdModes.NAMED:
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 = []
args = ["proxy-dns", "--port", str(constants.PROXY_DNS_PORT)]
else:
assert False, f"Unknown cfd_mode {cfd_mode}"
config = component_tests_config(cfd_mode=cfd_mode, run_proxy_dns=run_proxy_dns)
with start_cloudflared(tmp_path, config, cfd_pre_args=pre_args, cfd_args=args, new_process=True, capture_output=False):
if expect_tunnel:
wait_tunnel_ready()
else:
check_tunnel_not_connected()
verify_proxy_dns(expect_proxy_dns)
def verify_proxy_dns(should_be_running):
# Wait for the Proxy DNS listener to come up.
sleep(constants.BACKOFF_SECS)
had_failure = False
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.connect(('localhost', constants.PROXY_DNS_PORT))
sock.send(b"anything")
except:
if should_be_running:
assert False, "Expected Proxy DNS to be running, but it was not."
had_failure = True
finally:
sock.close()
if not should_be_running and not had_failure:
assert False, "Proxy DNS should not have been running, but it was."
+2 -2
View File
@@ -6,6 +6,7 @@ from time import sleep
import pytest
from flaky import flaky
from conftest import CfdModes
from util import start_cloudflared, wait_tunnel_ready, check_tunnel_not_connected
@@ -27,8 +28,7 @@ class TestReconnect:
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, named_tunnel=False)
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)
+3 -2
View File
@@ -8,6 +8,7 @@ from pathlib import Path
import pytest
import test_logging
from conftest import CfdModes
from util import start_cloudflared, wait_tunnel_ready
@@ -34,7 +35,7 @@ class TestServiceMode:
"hello-world": True,
"logfile": str(log_file),
}
config = component_tests_config(additional_config=additional_config, named_tunnel=False)
config = component_tests_config(additional_config=additional_config, cfd_mode=CfdModes.CLASSIC)
def assert_log_file():
test_logging.assert_log_in_file(log_file)
@@ -52,7 +53,7 @@ class TestServiceMode:
"loglevel": "debug",
"log-directory": str(log_dir),
}
config = component_tests_config(additional_config=additional_config, named_tunnel=False)
config = component_tests_config(additional_config=additional_config, cfd_mode=CfdModes.CLASSIC)
def assert_rotating_log():
test_logging.assert_log_to_dir(config, log_dir)
+73 -3
View File
@@ -1,6 +1,7 @@
package connection
import (
"context"
"fmt"
"io"
"net/http"
@@ -11,9 +12,15 @@ import (
"github.com/google/uuid"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/websocket"
)
const LogFieldConnIndex = "connIndex"
const (
lbProbeUserAgentPrefix = "Mozilla/5.0 (compatible; Cloudflare-Traffic-Manager/1.0; +https://www.cloudflare.com/traffic-manager/;"
LogFieldConnIndex = "connIndex"
)
var switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols, http.StatusText(http.StatusSwitchingProtocols))
type Config struct {
OriginProxy OriginProxy
@@ -87,9 +94,64 @@ func (t Type) String() string {
}
}
// OriginProxy is how data flows from cloudflared to the origin services running behind it.
type OriginProxy interface {
// If Proxy returns an error, the caller is responsible for writing the error status to ResponseWriter
Proxy(w ResponseWriter, req *http.Request, sourceConnectionType Type) error
ProxyHTTP(w ResponseWriter, req *http.Request, 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
}
// 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
}
// HTTPResponseReadWriteAcker is an HTTP implementation of ReadWriteAcker.
type HTTPResponseReadWriteAcker struct {
r io.Reader
w ResponseWriter
req *http.Request
}
// NewHTTPResponseReadWriterAcker returns a new instance of HTTPResponseReadWriteAcker.
func NewHTTPResponseReadWriterAcker(w ResponseWriter, req *http.Request) *HTTPResponseReadWriteAcker {
return &HTTPResponseReadWriteAcker{
r: req.Body,
w: w,
req: req,
}
}
func (h *HTTPResponseReadWriteAcker) Read(p []byte) (int, error) {
return h.r.Read(p)
}
func (h *HTTPResponseReadWriteAcker) Write(p []byte) (int, error) {
return h.w.Write(p)
}
// 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 {
resp := &http.Response{
Status: switchingProtocolText,
StatusCode: http.StatusSwitchingProtocols,
ContentLength: -1,
}
if secWebsocketKey := h.req.Header.Get("Sec-WebSocket-Key"); secWebsocketKey != "" {
resp.Header = websocket.NewResponseHeader(h.req)
}
return h.w.WriteRespHeaders(resp.StatusCode, resp.Header)
}
type ResponseWriter interface {
@@ -112,3 +174,11 @@ func IsServerSentEvent(headers http.Header) bool {
func uint8ToString(input uint8) string {
return strconv.FormatUint(uint64(input), 10)
}
func FindCfRayHeader(req *http.Request) string {
return req.Header.Get("Cf-Ray")
}
func IsLBProbeRequest(req *http.Request) bool {
return strings.HasPrefix(req.UserAgent(), lbProbeUserAgentPrefix)
}
+23 -7
View File
@@ -1,6 +1,7 @@
package connection
import (
"context"
"fmt"
"io"
"net/http"
@@ -11,6 +12,8 @@ import (
"github.com/gobwas/ws/wsutil"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/cloudflare/cloudflared/ingress"
)
const (
@@ -18,7 +21,8 @@ const (
)
var (
testConfig = &Config{
unusedWarpRoutingService = (*ingress.WarpRoutingService)(nil)
testConfig = &Config{
OriginProxy: &mockOriginProxy{},
GracePeriod: time.Millisecond * 100,
}
@@ -38,14 +42,17 @@ type testRequest struct {
isProxyError bool
}
type mockOriginProxy struct {
}
type mockOriginProxy struct{}
func (moc *mockOriginProxy) Proxy(w ResponseWriter, r *http.Request, sourceConnectionType Type) error {
if sourceConnectionType == TypeWebsocket {
return wsEndpoint(w, r)
func (moc *mockOriginProxy) ProxyHTTP(
w ResponseWriter,
req *http.Request,
isWebsocket bool,
) error {
if isWebsocket {
return wsEndpoint(w, req)
}
switch r.URL.Path {
switch req.URL.Path {
case "/ok":
originRespEndpoint(w, http.StatusOK, []byte(http.StatusText(http.StatusOK)))
case "/large_file":
@@ -60,6 +67,15 @@ func (moc *mockOriginProxy) Proxy(w ResponseWriter, r *http.Request, sourceConne
originRespEndpoint(w, http.StatusNotFound, []byte("page not found"))
}
return nil
}
func (moc *mockOriginProxy) ProxyTCP(
ctx context.Context,
rwa ReadWriteAcker,
r *TCPRequest,
) error {
return nil
}
type nowriter struct {
+4 -3
View File
@@ -33,6 +33,8 @@ type h2muxConnection struct {
gracefulShutdownC <-chan struct{}
stoppedGracefully bool
log *zerolog.Logger
// newRPCClientFunc allows us to mock RPCs during testing
newRPCClientFunc func(context.Context, io.ReadWriteCloser, *zerolog.Logger) NamedTunnelRPCClient
}
@@ -222,12 +224,11 @@ func (h *h2muxConnection) ServeStream(stream *h2mux.MuxedStream) error {
sourceConnectionType = TypeWebsocket
}
err := h.config.OriginProxy.Proxy(respWriter, req, sourceConnectionType)
err := h.config.OriginProxy.ProxyHTTP(respWriter, req, sourceConnectionType == TypeWebsocket)
if err != nil {
respWriter.WriteErrorResponse()
return err
}
return nil
return err
}
func (h *h2muxConnection) newRequest(stream *h2mux.MuxedStream) (*http.Request, error) {
+58 -15
View File
@@ -10,6 +10,7 @@ import (
"strings"
"sync"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"golang.org/x/net/http2"
@@ -26,7 +27,9 @@ const (
var errEdgeConnectionClosed = fmt.Errorf("connection with edge closed")
type http2Connection struct {
// HTTP2Connection represents a net.Conn that uses HTTP2 frames to proxy traffic from the edge to cloudflared on the
// origin.
type HTTP2Connection struct {
conn net.Conn
server *http2.Server
config *Config
@@ -38,6 +41,7 @@ type http2Connection struct {
// newRPCClientFunc allows us to mock RPCs during testing
newRPCClientFunc func(context.Context, io.ReadWriteCloser, *zerolog.Logger) NamedTunnelRPCClient
log *zerolog.Logger
activeRequestsWG sync.WaitGroup
connectedFuse ConnectedFuse
gracefulShutdownC <-chan struct{}
@@ -45,6 +49,7 @@ type http2Connection struct {
controlStreamErr error // result of running control stream handler
}
// NewHTTP2Connection returns a new instance of HTTP2Connection.
func NewHTTP2Connection(
conn net.Conn,
config *Config,
@@ -53,9 +58,10 @@ func NewHTTP2Connection(
observer *Observer,
connIndex uint8,
connectedFuse ConnectedFuse,
log *zerolog.Logger,
gracefulShutdownC <-chan struct{},
) *http2Connection {
return &http2Connection{
) *HTTP2Connection {
return &HTTP2Connection{
conn: conn,
server: &http2.Server{
MaxConcurrentStreams: math.MaxUint32,
@@ -68,11 +74,13 @@ func NewHTTP2Connection(
connIndex: connIndex,
newRPCClientFunc: newRegistrationRPCClient,
connectedFuse: connectedFuse,
log: log,
gracefulShutdownC: gracefulShutdownC,
}
}
func (c *http2Connection) Serve(ctx context.Context) error {
// Serve serves an HTTP2 server that the edge can talk to.
func (c *HTTP2Connection) Serve(ctx context.Context) error {
go func() {
<-ctx.Done()
c.close()
@@ -93,7 +101,7 @@ func (c *http2Connection) Serve(ctx context.Context) error {
}
}
func (c *http2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (c *HTTP2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.activeRequestsWG.Add(1)
defer c.activeRequestsWG.Done()
@@ -106,23 +114,47 @@ func (c *http2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
var proxyErr error
switch connType {
case TypeControlStream:
proxyErr = c.serveControlStream(r.Context(), respWriter)
c.controlStreamErr = proxyErr
case TypeWebsocket:
if err := c.serveControlStream(r.Context(), respWriter); err != nil {
c.controlStreamErr = err
c.log.Error().Err(err)
respWriter.WriteErrorResponse()
}
case TypeWebsocket, TypeHTTP:
stripWebsocketUpgradeHeader(r)
proxyErr = c.config.OriginProxy.Proxy(respWriter, r, TypeWebsocket)
if err := c.config.OriginProxy.ProxyHTTP(respWriter, r, connType == TypeWebsocket); err != nil {
err := fmt.Errorf("Failed to proxy HTTP: %w", err)
c.log.Error().Err(err)
respWriter.WriteErrorResponse()
}
case TypeTCP:
host, err := getRequestHost(r)
if err != nil {
err := fmt.Errorf(`cloudflared recieved a warp-routing request with an empty host value: %w`, err)
c.log.Error().Err(err)
respWriter.WriteErrorResponse()
}
rws := NewHTTPResponseReadWriterAcker(respWriter, r)
if err := c.config.OriginProxy.ProxyTCP(r.Context(), rws, &TCPRequest{
Dest: host,
CFRay: FindCfRayHeader(r),
LBProbe: IsLBProbeRequest(r),
}); err != nil {
respWriter.WriteErrorResponse()
}
default:
proxyErr = c.config.OriginProxy.Proxy(respWriter, r, connType)
}
if proxyErr != nil {
err := fmt.Errorf("Received unknown connection type: %s", connType)
c.log.Error().Err(err)
respWriter.WriteErrorResponse()
}
}
func (c *http2Connection) serveControlStream(ctx context.Context, respWriter *http2RespWriter) error {
func (c *HTTP2Connection) serveControlStream(ctx context.Context, respWriter *http2RespWriter) error {
rpcClient := c.newRPCClientFunc(ctx, respWriter, c.observer.log)
defer rpcClient.Close()
@@ -145,7 +177,7 @@ func (c *http2Connection) serveControlStream(ctx context.Context, respWriter *ht
return nil
}
func (c *http2Connection) close() {
func (c *HTTP2Connection) close() {
// Wait for all serve HTTP handlers to return
c.activeRequestsWG.Wait()
c.conn.Close()
@@ -287,3 +319,14 @@ func IsTCPStream(r *http.Request) bool {
func stripWebsocketUpgradeHeader(r *http.Request) {
r.Header.Del(InternalUpgradeHeader)
}
// getRequestHost returns the host of the http.Request.
func getRequestHost(r *http.Request) (string, error) {
if r.Host != "" {
return r.Host, nil
}
if r.URL != nil {
return r.URL.Host, nil
}
return "", errors.New("host not set in incoming request")
}
+3 -1
View File
@@ -26,9 +26,10 @@ var (
testTransport = http2.Transport{}
)
func newTestHTTP2Connection() (*http2Connection, net.Conn) {
func newTestHTTP2Connection() (*HTTP2Connection, net.Conn) {
edgeConn, originConn := net.Pipe()
var connIndex = uint8(0)
log := zerolog.Nop()
return NewHTTP2Connection(
originConn,
testConfig,
@@ -37,6 +38,7 @@ func newTestHTTP2Connection() (*http2Connection, net.Conn) {
NewObserver(&log, &log, false),
connIndex,
mockConnectedFuse{},
&log,
nil,
), edgeConn
}
+7
View File
@@ -111,6 +111,13 @@ func (o *Observer) sendConnectedEvent(connIndex uint8, location string) {
func (o *Observer) sendURL(url string) {
o.sendEvent(Event{EventType: SetURL, URL: url})
if !strings.HasPrefix(url, "https://") {
// We add https:// in the prefix for backwards compatibility as we used to do that with the old free tunnels
// and some tools (like `wrangler tail`) are regexp-ing for that specifically.
url = "https://" + url
}
o.metrics.userHostnamesCounts.WithLabelValues(url).Inc()
}
func (o *Observer) SendReconnect(connIndex uint8) {
+19
View File
@@ -6,9 +6,28 @@ import (
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/assert"
)
func TestSendUrl(t *testing.T) {
observer := NewObserver(&log, &log, false)
observer.sendURL("my-url.com")
assert.Equal(t, 1.0, getCounterValue(t, observer.metrics.userHostnamesCounts, "https://my-url.com"))
observer.sendURL("https://another-long-one.com")
assert.Equal(t, 1.0, getCounterValue(t, observer.metrics.userHostnamesCounts, "https://another-long-one.com"))
}
func getCounterValue(t *testing.T, metric *prometheus.CounterVec, val string) float64 {
var m = &dto.Metric{}
err := metric.WithLabelValues(val).Write(m)
assert.NoError(t, err)
return m.Counter.GetValue()
}
func TestRegisterServerLocation(t *testing.T) {
m := newTunnelMetrics()
tunnels := 20
+4 -1
View File
@@ -172,7 +172,10 @@ func NewProtocolSelector(
http2Percentage, err := fetchFunc()
if err != nil {
return nil, err
log.Err(err).Msg("Unable to lookup protocol. Defaulting to `http2`. If this fails, you can set `--protocol h2mux` in your cloudflared command.")
return &staticProtocolSelector{
current: HTTP2,
}, nil
}
if protocolFlag == HTTP2.String() {
if http2Percentage < 0 {
+2 -1
View File
@@ -158,7 +158,8 @@ func TestNewProtocolSelector(t *testing.T) {
protocol: "unknown",
fetchFunc: mockFetcherWithError(),
namedTunnelConfig: testNamedTunnelConfig,
wantErr: true,
expectedProtocol: HTTP2,
wantErr: false,
},
}
+45 -32
View File
@@ -10,6 +10,9 @@ import shutil
import hashlib
import requests
import tarfile
from os import listdir
from os.path import isfile, join
import re
from github import Github, GithubException, UnknownObjectException
@@ -135,7 +138,7 @@ def parse_args():
logging.error("Missing asset path")
is_valid = False
if not args.name:
if not args.name and not os.path.isdir(args.path):
logging.error("Missing asset name")
is_valid = False
@@ -161,6 +164,38 @@ def parse_args():
parser.print_usage()
exit(1)
def upload_asset(release, filepath, filename, release_version, kv_account_id, namespace_id, kv_api_token):
logging.info("Uploading asset: %s", filename)
release.upload_asset(filepath, name=filename)
# check and extract if the file is a tar and gzipped file (as is the case with the macos builds)
binary_path = filepath
if binary_path.endswith("tgz"):
try:
shutil.rmtree('cfd')
except OSError:
pass
zipfile = tarfile.open(binary_path, "r:gz")
zipfile.extractall('cfd') # specify which folder to extract to
zipfile.close()
binary_path = os.path.join(os.getcwd(), 'cfd', 'cloudflared')
# send the sha256 (the checksum) to workers kv
pkg_hash = get_sha256(binary_path)
send_hash(pkg_hash, filename, release_version, kv_account_id, namespace_id, kv_api_token)
# create the artifacts directory if it doesn't exist
artifact_path = os.path.join(os.getcwd(), 'artifacts')
if not os.path.isdir(artifact_path):
os.mkdir(artifact_path)
# copy the binary to the path
copy_path = os.path.join(artifact_path, filename)
try:
shutil.copy(filepath, copy_path)
except shutil.SameFileError:
pass # the macOS release copy fails with being the same file (already in the artifacts directory)
def main():
""" Attempts to upload Asset to Github Release. Creates Release if it doesnt exist """
@@ -174,40 +209,18 @@ def main():
logging.info("Skipping asset upload because of dry-run")
return
release.upload_asset(args.path, name=args.name)
# check and extract if the file is a tar and gzipped file (as is the case with the macos builds)
binary_path = args.path
if binary_path.endswith("tgz"):
try:
shutil.rmtree('cfd')
except OSError as e:
pass
zipfile = tarfile.open(binary_path, "r:gz")
zipfile.extractall('cfd') # specify which folder to extract to
zipfile.close()
binary_path = os.path.join(os.getcwd(), 'cfd', 'cloudflared')
# send the sha256 (the checksum) to workers kv
pkg_hash = get_sha256(binary_path)
send_hash(pkg_hash, args.name, args.release_version, args.kv_account_id, args.namespace_id, args.kv_api_token)
# create the artifacts directory if it doesn't exist
artifact_path = os.path.join(os.getcwd(), 'artifacts')
if not os.path.isdir(artifact_path):
os.mkdir(artifact_path)
# copy the binary to the path
copy_path = os.path.join(artifact_path, args.name)
try:
shutil.copy(args.path, copy_path)
except shutil.SameFileError:
pass # the macOS release copy fails with being the same file (already in the artifacts directory). Catching to ignore.
if os.path.isdir(args.path):
onlyfiles = [f for f in listdir(args.path) if isfile(join(args.path, f))]
for filename in onlyfiles:
binary_path = os.path.join(args.path, filename)
upload_asset(release, binary_path, filename, args.release_version, args.kv_account_id, args.namespace_id,
args.kv_api_token)
else:
upload_asset(release, args.path, args.name, args.release_version, args.kv_account_id, args.namespace_id,
args.kv_api_token)
except Exception as e:
logging.exception(e)
exit(1)
main()
-2
View File
@@ -1,7 +1,6 @@
package ingress
import (
"fmt"
"net"
"net/http"
@@ -9,7 +8,6 @@ import (
)
var (
switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols, http.StatusText(http.StatusSwitchingProtocols))
errUnsupportedConnectionType = errors.New("internal error: unsupported connection type")
)
+79 -94
View File
@@ -7,7 +7,6 @@ import (
"io"
"net/http"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/rs/zerolog"
@@ -20,13 +19,15 @@ import (
)
const (
// TagHeaderNamePrefix indicates a Cloudflared Warp Tag prefix that gets appended for warp traffic stream headers.
TagHeaderNamePrefix = "Cf-Warp-Tag-"
LogFieldCFRay = "cfRay"
LogFieldRule = "ingressRule"
LogFieldOriginService = "originService"
)
type proxy struct {
// Proxy represents a means to Proxy between cloudflared and the origin services.
type Proxy struct {
ingressRules ingress.Ingress
warpRouting *ingress.WarpRoutingService
tags []tunnelpogs.Tag
@@ -34,15 +35,14 @@ type proxy struct {
bufferPool *bufferPool
}
var switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols, http.StatusText(http.StatusSwitchingProtocols))
// NewOriginProxy returns a new instance of the Proxy struct.
func NewOriginProxy(
ingressRules ingress.Ingress,
warpRouting *ingress.WarpRoutingService,
tags []tunnelpogs.Tag,
log *zerolog.Logger) connection.OriginProxy {
return &proxy{
log *zerolog.Logger,
) *Proxy {
return &Proxy{
ingressRules: ingressRules,
warpRouting: warpRouting,
tags: tags,
@@ -51,41 +51,18 @@ func NewOriginProxy(
}
}
// Caller is responsible for writing any error to ResponseWriter
func (p *proxy) Proxy(w connection.ResponseWriter, req *http.Request, sourceConnectionType connection.Type) error {
// ProxyHTTP further depends on ingress rules to establish a connection with the origin service. This may be
// a simple roundtrip or a tcp/websocket dial depending on ingres rule setup.
func (p *Proxy) ProxyHTTP(
w connection.ResponseWriter,
req *http.Request,
isWebsocket bool,
) error {
incrementRequests()
defer decrementConcurrentRequests()
cfRay := findCfRayHeader(req)
lbProbe := isLBProbeRequest(req)
serveCtx, cancel := context.WithCancel(req.Context())
defer cancel()
p.appendTagHeaders(req)
if sourceConnectionType == connection.TypeTCP {
if p.warpRouting == nil {
err := errors.New(`cloudflared received a request from WARP client, but your configuration has disabled ingress from WARP clients. To enable this, set "warp-routing:\n\t enabled: true" in your config.yaml`)
p.log.Error().Msg(err.Error())
return err
}
logFields := logFields{
cfRay: cfRay,
lbProbe: lbProbe,
rule: ingress.ServiceWarpRouting,
}
host, err := getRequestHost(req)
if err != nil {
err = fmt.Errorf(`cloudflared recieved a warp-routing request with an empty host value: %v`, err)
return err
}
if err := p.proxyStreamRequest(serveCtx, w, host, req, p.warpRouting.Proxy, logFields); err != nil {
p.logRequestError(err, cfRay, "", ingress.ServiceWarpRouting)
return err
}
return nil
}
cfRay := connection.FindCfRayHeader(req)
lbProbe := connection.IsLBProbeRequest(req)
rule, ruleNum := p.ingressRules.FindMatchingRule(req.Host, req.URL.Path)
logFields := logFields{
@@ -97,8 +74,14 @@ func (p *proxy) Proxy(w connection.ResponseWriter, req *http.Request, sourceConn
switch originProxy := rule.Service.(type) {
case ingress.HTTPOriginProxy:
if err := p.proxyHTTPRequest(w, req, originProxy, sourceConnectionType == connection.TypeWebsocket,
rule.Config.DisableChunkedEncoding, logFields); err != nil {
if err := p.proxyHTTPRequest(
w,
req,
originProxy,
isWebsocket,
rule.Config.DisableChunkedEncoding,
logFields,
); err != nil {
rule, srv := ruleField(p.ingressRules, ruleNum)
p.logRequestError(err, cfRay, rule, srv)
return err
@@ -110,7 +93,9 @@ func (p *proxy) Proxy(w connection.ResponseWriter, req *http.Request, sourceConn
if err != nil {
return err
}
if err := p.proxyStreamRequest(serveCtx, w, dest, req, originProxy, logFields); err != nil {
rws := connection.NewHTTPResponseReadWriterAcker(w, req)
if err := p.proxyStream(req.Context(), rws, dest, originProxy, logFields); err != nil {
rule, srv := ruleField(p.ingressRules, ruleNum)
p.logRequestError(err, cfRay, rule, srv)
return err
@@ -121,24 +106,36 @@ func (p *proxy) Proxy(w connection.ResponseWriter, req *http.Request, sourceConn
}
}
func getDestFromRule(rule *ingress.Rule, req *http.Request) (string, error) {
switch rule.Service.String() {
case ingress.ServiceBastion:
return carrier.ResolveBastionDest(req)
default:
return rule.Service.String(), nil
}
}
// ProxyTCP proxies to a TCP connection between the origin service and cloudflared.
func (p *Proxy) ProxyTCP(
ctx context.Context,
rwa connection.ReadWriteAcker,
req *connection.TCPRequest,
) error {
incrementRequests()
defer decrementConcurrentRequests()
// getRequestHost returns the host of the http.Request.
func getRequestHost(r *http.Request) (string, error) {
if r.Host != "" {
return r.Host, nil
if p.warpRouting == nil {
err := errors.New(`cloudflared received a request from WARP client, but your configuration has disabled ingress from WARP clients. To enable this, set "warp-routing:\n\t enabled: true" in your config.yaml`)
p.log.Error().Msg(err.Error())
return err
}
if r.URL != nil {
return r.URL.Host, nil
serveCtx, cancel := context.WithCancel(ctx)
defer cancel()
logFields := logFields{
cfRay: req.CFRay,
lbProbe: req.LBProbe,
rule: ingress.ServiceWarpRouting,
}
return "", errors.New("host not set in incoming request")
if err := p.proxyStream(serveCtx, rwa, req.Dest, p.warpRouting.Proxy, logFields); err != nil {
p.logRequestError(err, req.CFRay, "", ingress.ServiceWarpRouting)
return err
}
return nil
}
func ruleField(ing ingress.Ingress, ruleNum int) (ruleID string, srv string) {
@@ -149,13 +146,15 @@ func ruleField(ing ingress.Ingress, ruleNum int) (ruleID string, srv string) {
return fmt.Sprintf("%d", ruleNum), srv
}
func (p *proxy) proxyHTTPRequest(
// ProxyHTTPRequest proxies requests of underlying type http and websocket to the origin service.
func (p *Proxy) proxyHTTPRequest(
w connection.ResponseWriter,
req *http.Request,
httpService ingress.HTTPOriginProxy,
isWebsocket bool,
disableChunkedEncoding bool,
fields logFields) error {
fields logFields,
) error {
roundTripReq := req
if isWebsocket {
roundTripReq = req.Clone(req.Context())
@@ -214,17 +213,17 @@ func (p *proxy) proxyHTTPRequest(
defer p.bufferPool.Put(buf)
_, _ = io.CopyBuffer(w, resp.Body, buf)
}
p.logOriginResponse(resp, fields)
return nil
}
// proxyStreamRequest first establish a connection with origin, then it writes the status code and headers, and finally it streams data between
// eyeball and origin.
func (p *proxy) proxyStreamRequest(
serveCtx context.Context,
w connection.ResponseWriter,
// proxyStream proxies type TCP and other underlying types if the connection is defined as a stream oriented
// ingress rule.
func (p *Proxy) proxyStream(
ctx context.Context,
rwa connection.ReadWriteAcker,
dest string,
req *http.Request,
connectionProxy ingress.StreamBasedOriginProxy,
fields logFields,
) error {
@@ -233,21 +232,11 @@ func (p *proxy) proxyStreamRequest(
return err
}
resp := &http.Response{
Status: switchingProtocolText,
StatusCode: http.StatusSwitchingProtocols,
ContentLength: -1,
}
if secWebsocketKey := req.Header.Get("Sec-WebSocket-Key"); secWebsocketKey != "" {
resp.Header = websocket.NewResponseHeader(req)
}
if err = w.WriteRespHeaders(resp.StatusCode, resp.Header); err != nil {
if err := rwa.AckConnection(); err != nil {
return err
}
streamCtx, cancel := context.WithCancel(serveCtx)
streamCtx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
@@ -256,12 +245,7 @@ func (p *proxy) proxyStreamRequest(
originConn.Close()
}()
eyeballStream := &bidirectionalStream{
writer: w,
reader: req.Body,
}
originConn.Stream(serveCtx, eyeballStream, p.log)
p.logOriginResponse(resp, fields)
originConn.Stream(ctx, rwa, p.log)
return nil
}
@@ -278,7 +262,7 @@ func (wr *bidirectionalStream) Write(p []byte) (n int, err error) {
return wr.writer.Write(p)
}
func (p *proxy) writeEventStream(w connection.ResponseWriter, respBody io.ReadCloser) {
func (p *Proxy) writeEventStream(w connection.ResponseWriter, respBody io.ReadCloser) {
reader := bufio.NewReader(respBody)
for {
line, err := reader.ReadBytes('\n')
@@ -289,7 +273,7 @@ func (p *proxy) writeEventStream(w connection.ResponseWriter, respBody io.ReadCl
}
}
func (p *proxy) appendTagHeaders(r *http.Request) {
func (p *Proxy) appendTagHeaders(r *http.Request) {
for _, tag := range p.tags {
r.Header.Add(TagHeaderNamePrefix+tag.Name, tag.Value)
}
@@ -301,7 +285,7 @@ type logFields struct {
rule interface{}
}
func (p *proxy) logRequest(r *http.Request, fields logFields) {
func (p *Proxy) logRequest(r *http.Request, fields logFields) {
if fields.cfRay != "" {
p.log.Debug().Msgf("CF-RAY: %s %s %s %s", fields.cfRay, r.Method, r.URL, r.Proto)
} else if fields.lbProbe {
@@ -324,7 +308,7 @@ func (p *proxy) logRequest(r *http.Request, fields logFields) {
}
}
func (p *proxy) logOriginResponse(resp *http.Response, fields logFields) {
func (p *Proxy) logOriginResponse(resp *http.Response, fields logFields) {
responseByCode.WithLabelValues(strconv.Itoa(resp.StatusCode)).Inc()
if fields.cfRay != "" {
p.log.Debug().Msgf("CF-RAY: %s Status: %s served by ingress %d", fields.cfRay, resp.Status, fields.rule)
@@ -342,7 +326,7 @@ func (p *proxy) logOriginResponse(resp *http.Response, fields logFields) {
}
}
func (p *proxy) logRequestError(err error, cfRay string, rule, service string) {
func (p *Proxy) logRequestError(err error, cfRay string, rule, service string) {
requestErrors.Inc()
log := p.log.Error().Err(err)
if cfRay != "" {
@@ -357,10 +341,11 @@ func (p *proxy) logRequestError(err error, cfRay string, rule, service string) {
log.Msg("")
}
func findCfRayHeader(req *http.Request) string {
return req.Header.Get("Cf-Ray")
}
func isLBProbeRequest(req *http.Request) bool {
return strings.HasPrefix(req.UserAgent(), lbProbeUserAgentPrefix)
func getDestFromRule(rule *ingress.Rule, req *http.Request) (string, error) {
switch rule.Service.String() {
case ingress.ServiceBastion:
return carrier.ResolveBastionDest(req)
default:
return rule.Service.String(), nil
}
}
+22 -9
View File
@@ -46,6 +46,10 @@ func newMockHTTPRespWriter() *mockHTTPRespWriter {
}
}
func (w *mockHTTPRespWriter) WriteResponse() error {
return nil
}
func (w *mockHTTPRespWriter) WriteRespHeaders(status int, header http.Header) error {
w.WriteHeader(status)
for header, val := range header {
@@ -146,7 +150,7 @@ func testProxyHTTP(proxy connection.OriginProxy) func(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", nil)
require.NoError(t, err)
err = proxy.Proxy(responseWriter, req, connection.TypeHTTP)
err = proxy.ProxyHTTP(responseWriter, req, false)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, responseWriter.Code)
@@ -170,7 +174,7 @@ func testProxyWebsocket(proxy connection.OriginProxy) func(t *testing.T) {
errGroup, ctx := errgroup.WithContext(ctx)
errGroup.Go(func() error {
err = proxy.Proxy(responseWriter, req, connection.TypeWebsocket)
err = proxy.ProxyHTTP(responseWriter, req, true)
require.NoError(t, err)
require.Equal(t, http.StatusSwitchingProtocols, responseWriter.Code)
@@ -231,7 +235,7 @@ func testProxySSE(proxy connection.OriginProxy) func(t *testing.T) {
wg.Add(1)
go func() {
defer wg.Done()
err = proxy.Proxy(responseWriter, req, connection.TypeHTTP)
err = proxy.ProxyHTTP(responseWriter, req, false)
require.NoError(t, err)
require.Equal(t, http.StatusOK, responseWriter.Code)
@@ -330,7 +334,7 @@ func runIngressTestScenarios(t *testing.T, unvalidatedIngress []config.Unvalidat
req, err := http.NewRequest(http.MethodGet, test.url, nil)
require.NoError(t, err)
err = proxy.Proxy(responseWriter, req, connection.TypeHTTP)
err = proxy.ProxyHTTP(responseWriter, req, false)
require.NoError(t, err)
assert.Equal(t, test.expectedStatus, responseWriter.Code)
@@ -358,7 +362,7 @@ func (errorOriginTransport) RoundTrip(*http.Request) (*http.Response, error) {
}
func TestProxyError(t *testing.T) {
ingress := ingress.Ingress{
ing := ingress.Ingress{
Rules: []ingress.Rule{
{
Hostname: "*",
@@ -372,13 +376,13 @@ func TestProxyError(t *testing.T) {
log := zerolog.Nop()
proxy := NewOriginProxy(ingress, unusedWarpRoutingService, testTags, &log)
proxy := NewOriginProxy(ing, unusedWarpRoutingService, testTags, &log)
responseWriter := newMockHTTPRespWriter()
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1", nil)
assert.NoError(t, err)
assert.Error(t, proxy.Proxy(responseWriter, req, connection.TypeHTTP))
assert.Error(t, proxy.ProxyHTTP(responseWriter, req, false))
}
type replayer struct {
@@ -617,6 +621,7 @@ func TestConnections(t *testing.T) {
ingressRule.StartOrigins(&wg, logger, ctx.Done(), errC)
proxy := NewOriginProxy(ingressRule, test.args.warpRoutingService, testTags, logger)
dest := ln.Addr().String()
req, err := http.NewRequest(
http.MethodGet,
test.args.ingressServiceScheme+ln.Addr().String(),
@@ -634,8 +639,12 @@ func TestConnections(t *testing.T) {
replayer.Write(resp)
}()
}
err = proxy.Proxy(respWriter, req, test.args.connectionType)
if test.args.connectionType == connection.TypeTCP {
rws := connection.NewHTTPResponseReadWriterAcker(respWriter, req)
err = proxy.ProxyTCP(ctx, rws, &connection.TCPRequest{Dest: dest})
} else {
err = proxy.ProxyHTTP(respWriter, req, test.args.connectionType == connection.TypeWebsocket)
}
cancel()
assert.Equal(t, test.want.err, err != nil)
@@ -829,6 +838,10 @@ func newTCPRespWriter(w io.Writer) *mockTCPRespWriter {
}
}
func (m *mockTCPRespWriter) Read(p []byte) (n int, err error) {
return len(p), nil
}
func (m *mockTCPRespWriter) Write(p []byte) (n int, err error) {
return m.w.Write(p)
}
+1 -1
View File
@@ -26,7 +26,6 @@ import (
const (
dialTimeout = 15 * time.Second
lbProbeUserAgentPrefix = "Mozilla/5.0 (compatible; Cloudflare-Traffic-Manager/1.0; +https://www.cloudflare.com/traffic-manager/;"
FeatureSerializedHeaders = "serialized_headers"
FeatureQuickReconnects = "quick_reconnects"
)
@@ -417,6 +416,7 @@ func ServeHTTP2(
config.Observer,
connIndex,
connectedFuse,
config.Log,
gracefulShutdownC,
)
+88
View File
@@ -0,0 +1,88 @@
package quic
import (
capnp "zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/pogs"
"github.com/cloudflare/cloudflared/quic/schema"
)
// ConnectionType indicates the type of underlying connection proxied within the QUIC stream.
type ConnectionType uint16
const (
ConnectionTypeHTTP ConnectionType = iota
ConnectionTypeWebsocket
ConnectionTypeTCP
)
// ConnectRequest is the representation of metadata sent at the start of a QUIC application handshake.
type ConnectRequest struct {
Dest string `capnp:"dest"`
Type ConnectionType `capnp:"type"`
Metadata []Metadata `capnp:"metadata"`
}
// Metadata is a representation of key value based data sent via RequestMeta.
type Metadata struct {
Key string `capnp:"key"`
Val string `capnp:"val"`
}
func (r *ConnectRequest) fromPogs(msg *capnp.Message) error {
metadata, err := schema.ReadRootConnectRequest(msg)
if err != nil {
return err
}
return pogs.Extract(r, schema.ConnectRequest_TypeID, metadata.Struct)
}
func (r *ConnectRequest) toPogs() (*capnp.Message, error) {
msg, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
if err != nil {
return nil, err
}
root, err := schema.NewRootConnectRequest(seg)
if err != nil {
return nil, err
}
if err := pogs.Insert(schema.ConnectRequest_TypeID, root.Struct, r); err != nil {
return nil, err
}
return msg, nil
}
// ConnectResponse is a representation of metadata sent as a response to a QUIC application handshake.
type ConnectResponse struct {
Error string `capnp:"error"`
Metadata []Metadata `capnp:"metadata"`
}
func (r *ConnectResponse) fromPogs(msg *capnp.Message) error {
metadata, err := schema.ReadRootConnectResponse(msg)
if err != nil {
return err
}
return pogs.Extract(r, schema.ConnectResponse_TypeID, metadata.Struct)
}
func (r *ConnectResponse) toPogs() (*capnp.Message, error) {
msg, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
if err != nil {
return nil, err
}
root, err := schema.NewRootConnectResponse(seg)
if err != nil {
return nil, err
}
if err := pogs.Insert(schema.ConnectResponse_TypeID, root.Struct, r); err != nil {
return nil, err
}
return msg, nil
}
+115
View File
@@ -0,0 +1,115 @@
package quic
import (
"bytes"
"fmt"
"io"
capnp "zombiezen.com/go/capnproto2"
)
// protocolSignature is a custom protocol signature to ensure that whoever performs a handshake does not write data
// before writing the metadata.
var protocolSignature = []byte{0x0A, 0x36, 0xCD, 0x12, 0xA1, 0x3E}
// ReadConnectRequestData reads the handshake data from a QUIC stream.
func ReadConnectRequestData(stream io.Reader) (*ConnectRequest, error) {
if err := verifySignature(stream); err != nil {
return nil, err
}
msg, err := capnp.NewDecoder(stream).Decode()
if err != nil {
return nil, err
}
r := &ConnectRequest{}
if err := r.fromPogs(msg); err != nil {
return nil, err
}
return r, nil
}
// WriteConnectRequestData writes requestMeta to a stream.
func WriteConnectRequestData(stream io.Writer, dest string, connectionType ConnectionType, metadata ...Metadata) error {
connectRequest := &ConnectRequest{
Dest: dest,
Type: connectionType,
Metadata: metadata,
}
msg, err := connectRequest.toPogs()
if err != nil {
return err
}
if err := writePreamble(stream); err != nil {
return err
}
return capnp.NewEncoder(stream).Encode(msg)
}
// ReadConnectResponseData reads the response to a RequestMeta in a stream.
func ReadConnectResponseData(stream io.Reader) (*ConnectResponse, error) {
if err := verifySignature(stream); err != nil {
return nil, err
}
msg, err := capnp.NewDecoder(stream).Decode()
if err != nil {
return nil, err
}
r := &ConnectResponse{}
if err := r.fromPogs(msg); err != nil {
return nil, err
}
return r, nil
}
// WriteConnectResponseData writes response to a QUIC stream.
func WriteConnectResponseData(stream io.Writer, respErr error, metadata ...Metadata) error {
var connectResponse *ConnectResponse
if respErr != nil {
connectResponse = &ConnectResponse{
Error: respErr.Error(),
}
} else {
connectResponse = &ConnectResponse{
Metadata: metadata,
}
}
msg, err := connectResponse.toPogs()
if err != nil {
return err
}
if err := writePreamble(stream); err != nil {
return err
}
return capnp.NewEncoder(stream).Encode(msg)
}
func writePreamble(stream io.Writer) error {
return writeSignature(stream)
// TODO : TUN-4613 Write protocol version here
}
func writeSignature(stream io.Writer) error {
_, err := stream.Write(protocolSignature)
return err
}
func verifySignature(stream io.Reader) error {
signature := make([]byte, len(protocolSignature))
if _, err := io.ReadFull(stream, signature); err != nil {
return err
}
if !bytes.Equal(signature[0:], protocolSignature) {
return fmt.Errorf("Wrong signature: %v", signature)
}
return nil
}
+88
View File
@@ -0,0 +1,88 @@
package quic
import (
"bytes"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestConnectRequestData(t *testing.T) {
var tests = []struct {
name string
hostname string
connectionType ConnectionType
metadata []Metadata
}{
{
name: "Signature verified and request metadata is unmarshaled and read correctly",
hostname: "tunnel.com",
connectionType: ConnectionTypeHTTP,
metadata: []Metadata{
Metadata{
Key: "key",
Val: "1234",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
b := &bytes.Buffer{}
err := WriteConnectRequestData(b, test.hostname, test.connectionType, test.metadata...)
require.NoError(t, err)
reqMeta, err := ReadConnectRequestData(b)
require.NoError(t, err)
assert.Equal(t, test.metadata, reqMeta.Metadata)
assert.Equal(t, test.hostname, reqMeta.Dest)
assert.Equal(t, test.connectionType, reqMeta.Type)
})
}
}
func TestConnectResponseMeta(t *testing.T) {
var tests = []struct {
name string
err error
metadata []Metadata
}{
{
name: "Signature verified and response metadata is unmarshaled and read correctly",
metadata: []Metadata{
Metadata{
Key: "key",
Val: "1234",
},
},
},
{
name: "If error is not empty, other fields should be blank",
err: errors.New("something happened"),
metadata: []Metadata{
Metadata{
Key: "key",
Val: "1234",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
b := &bytes.Buffer{}
err := WriteConnectResponseData(b, test.err, test.metadata...)
require.NoError(t, err)
respMeta, err := ReadConnectResponseData(b)
require.NoError(t, err)
if respMeta.Error == "" {
assert.Equal(t, test.metadata, respMeta.Metadata)
} else {
assert.Equal(t, 0, len(respMeta.Metadata))
}
})
}
}
+28
View File
@@ -0,0 +1,28 @@
using Go = import "/go.capnp";
@0xb29021ef7421cc32;
$Go.package("schema");
$Go.import("schema");
struct ConnectRequest{
dest @0 :Text;
type @1 :ConnectionType;
metadata @2 :List(Metadata);
}
enum ConnectionType{
http @0;
websocket @1;
tcp @2;
}
struct Metadata {
key @0 :Text;
val @1 :Text;
}
struct ConnectResponse{
error @0 :Text;
metadata @1 :List(Metadata);
}
+395
View File
@@ -0,0 +1,395 @@
// Code generated by capnpc-go. DO NOT EDIT.
package schema
import (
capnp "zombiezen.com/go/capnproto2"
text "zombiezen.com/go/capnproto2/encoding/text"
schemas "zombiezen.com/go/capnproto2/schemas"
)
type ConnectRequest struct{ capnp.Struct }
// ConnectRequest_TypeID is the unique identifier for the type ConnectRequest.
const ConnectRequest_TypeID = 0xc47116a1045e4061
func NewConnectRequest(s *capnp.Segment) (ConnectRequest, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2})
return ConnectRequest{st}, err
}
func NewRootConnectRequest(s *capnp.Segment) (ConnectRequest, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2})
return ConnectRequest{st}, err
}
func ReadRootConnectRequest(msg *capnp.Message) (ConnectRequest, error) {
root, err := msg.RootPtr()
return ConnectRequest{root.Struct()}, err
}
func (s ConnectRequest) String() string {
str, _ := text.Marshal(0xc47116a1045e4061, s.Struct)
return str
}
func (s ConnectRequest) Dest() (string, error) {
p, err := s.Struct.Ptr(0)
return p.Text(), err
}
func (s ConnectRequest) HasDest() bool {
p, err := s.Struct.Ptr(0)
return p.IsValid() || err != nil
}
func (s ConnectRequest) DestBytes() ([]byte, error) {
p, err := s.Struct.Ptr(0)
return p.TextBytes(), err
}
func (s ConnectRequest) SetDest(v string) error {
return s.Struct.SetText(0, v)
}
func (s ConnectRequest) Type() ConnectionType {
return ConnectionType(s.Struct.Uint16(0))
}
func (s ConnectRequest) SetType(v ConnectionType) {
s.Struct.SetUint16(0, uint16(v))
}
func (s ConnectRequest) Metadata() (Metadata_List, error) {
p, err := s.Struct.Ptr(1)
return Metadata_List{List: p.List()}, err
}
func (s ConnectRequest) HasMetadata() bool {
p, err := s.Struct.Ptr(1)
return p.IsValid() || err != nil
}
func (s ConnectRequest) SetMetadata(v Metadata_List) error {
return s.Struct.SetPtr(1, v.List.ToPtr())
}
// NewMetadata sets the metadata field to a newly
// allocated Metadata_List, preferring placement in s's segment.
func (s ConnectRequest) NewMetadata(n int32) (Metadata_List, error) {
l, err := NewMetadata_List(s.Struct.Segment(), n)
if err != nil {
return Metadata_List{}, err
}
err = s.Struct.SetPtr(1, l.List.ToPtr())
return l, err
}
// ConnectRequest_List is a list of ConnectRequest.
type ConnectRequest_List struct{ capnp.List }
// NewConnectRequest creates a new list of ConnectRequest.
func NewConnectRequest_List(s *capnp.Segment, sz int32) (ConnectRequest_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2}, sz)
return ConnectRequest_List{l}, err
}
func (s ConnectRequest_List) At(i int) ConnectRequest { return ConnectRequest{s.List.Struct(i)} }
func (s ConnectRequest_List) Set(i int, v ConnectRequest) error { return s.List.SetStruct(i, v.Struct) }
func (s ConnectRequest_List) String() string {
str, _ := text.MarshalList(0xc47116a1045e4061, s.List)
return str
}
// ConnectRequest_Promise is a wrapper for a ConnectRequest promised by a client call.
type ConnectRequest_Promise struct{ *capnp.Pipeline }
func (p ConnectRequest_Promise) Struct() (ConnectRequest, error) {
s, err := p.Pipeline.Struct()
return ConnectRequest{s}, err
}
type ConnectionType uint16
// ConnectionType_TypeID is the unique identifier for the type ConnectionType.
const ConnectionType_TypeID = 0xc52e1bac26d379c8
// Values of ConnectionType.
const (
ConnectionType_http ConnectionType = 0
ConnectionType_websocket ConnectionType = 1
ConnectionType_tcp ConnectionType = 2
)
// String returns the enum's constant name.
func (c ConnectionType) String() string {
switch c {
case ConnectionType_http:
return "http"
case ConnectionType_websocket:
return "websocket"
case ConnectionType_tcp:
return "tcp"
default:
return ""
}
}
// ConnectionTypeFromString returns the enum value with a name,
// or the zero value if there's no such value.
func ConnectionTypeFromString(c string) ConnectionType {
switch c {
case "http":
return ConnectionType_http
case "websocket":
return ConnectionType_websocket
case "tcp":
return ConnectionType_tcp
default:
return 0
}
}
type ConnectionType_List struct{ capnp.List }
func NewConnectionType_List(s *capnp.Segment, sz int32) (ConnectionType_List, error) {
l, err := capnp.NewUInt16List(s, sz)
return ConnectionType_List{l.List}, err
}
func (l ConnectionType_List) At(i int) ConnectionType {
ul := capnp.UInt16List{List: l.List}
return ConnectionType(ul.At(i))
}
func (l ConnectionType_List) Set(i int, v ConnectionType) {
ul := capnp.UInt16List{List: l.List}
ul.Set(i, uint16(v))
}
type Metadata struct{ capnp.Struct }
// Metadata_TypeID is the unique identifier for the type Metadata.
const Metadata_TypeID = 0xe1446b97bfd1cd37
func NewMetadata(s *capnp.Segment) (Metadata, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
return Metadata{st}, err
}
func NewRootMetadata(s *capnp.Segment) (Metadata, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
return Metadata{st}, err
}
func ReadRootMetadata(msg *capnp.Message) (Metadata, error) {
root, err := msg.RootPtr()
return Metadata{root.Struct()}, err
}
func (s Metadata) String() string {
str, _ := text.Marshal(0xe1446b97bfd1cd37, s.Struct)
return str
}
func (s Metadata) Key() (string, error) {
p, err := s.Struct.Ptr(0)
return p.Text(), err
}
func (s Metadata) HasKey() bool {
p, err := s.Struct.Ptr(0)
return p.IsValid() || err != nil
}
func (s Metadata) KeyBytes() ([]byte, error) {
p, err := s.Struct.Ptr(0)
return p.TextBytes(), err
}
func (s Metadata) SetKey(v string) error {
return s.Struct.SetText(0, v)
}
func (s Metadata) Val() (string, error) {
p, err := s.Struct.Ptr(1)
return p.Text(), err
}
func (s Metadata) HasVal() bool {
p, err := s.Struct.Ptr(1)
return p.IsValid() || err != nil
}
func (s Metadata) ValBytes() ([]byte, error) {
p, err := s.Struct.Ptr(1)
return p.TextBytes(), err
}
func (s Metadata) SetVal(v string) error {
return s.Struct.SetText(1, v)
}
// Metadata_List is a list of Metadata.
type Metadata_List struct{ capnp.List }
// NewMetadata creates a new list of Metadata.
func NewMetadata_List(s *capnp.Segment, sz int32) (Metadata_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2}, sz)
return Metadata_List{l}, err
}
func (s Metadata_List) At(i int) Metadata { return Metadata{s.List.Struct(i)} }
func (s Metadata_List) Set(i int, v Metadata) error { return s.List.SetStruct(i, v.Struct) }
func (s Metadata_List) String() string {
str, _ := text.MarshalList(0xe1446b97bfd1cd37, s.List)
return str
}
// Metadata_Promise is a wrapper for a Metadata promised by a client call.
type Metadata_Promise struct{ *capnp.Pipeline }
func (p Metadata_Promise) Struct() (Metadata, error) {
s, err := p.Pipeline.Struct()
return Metadata{s}, err
}
type ConnectResponse struct{ capnp.Struct }
// ConnectResponse_TypeID is the unique identifier for the type ConnectResponse.
const ConnectResponse_TypeID = 0xb1032ec91cef8727
func NewConnectResponse(s *capnp.Segment) (ConnectResponse, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
return ConnectResponse{st}, err
}
func NewRootConnectResponse(s *capnp.Segment) (ConnectResponse, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
return ConnectResponse{st}, err
}
func ReadRootConnectResponse(msg *capnp.Message) (ConnectResponse, error) {
root, err := msg.RootPtr()
return ConnectResponse{root.Struct()}, err
}
func (s ConnectResponse) String() string {
str, _ := text.Marshal(0xb1032ec91cef8727, s.Struct)
return str
}
func (s ConnectResponse) Error() (string, error) {
p, err := s.Struct.Ptr(0)
return p.Text(), err
}
func (s ConnectResponse) HasError() bool {
p, err := s.Struct.Ptr(0)
return p.IsValid() || err != nil
}
func (s ConnectResponse) ErrorBytes() ([]byte, error) {
p, err := s.Struct.Ptr(0)
return p.TextBytes(), err
}
func (s ConnectResponse) SetError(v string) error {
return s.Struct.SetText(0, v)
}
func (s ConnectResponse) Metadata() (Metadata_List, error) {
p, err := s.Struct.Ptr(1)
return Metadata_List{List: p.List()}, err
}
func (s ConnectResponse) HasMetadata() bool {
p, err := s.Struct.Ptr(1)
return p.IsValid() || err != nil
}
func (s ConnectResponse) SetMetadata(v Metadata_List) error {
return s.Struct.SetPtr(1, v.List.ToPtr())
}
// NewMetadata sets the metadata field to a newly
// allocated Metadata_List, preferring placement in s's segment.
func (s ConnectResponse) NewMetadata(n int32) (Metadata_List, error) {
l, err := NewMetadata_List(s.Struct.Segment(), n)
if err != nil {
return Metadata_List{}, err
}
err = s.Struct.SetPtr(1, l.List.ToPtr())
return l, err
}
// ConnectResponse_List is a list of ConnectResponse.
type ConnectResponse_List struct{ capnp.List }
// NewConnectResponse creates a new list of ConnectResponse.
func NewConnectResponse_List(s *capnp.Segment, sz int32) (ConnectResponse_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2}, sz)
return ConnectResponse_List{l}, err
}
func (s ConnectResponse_List) At(i int) ConnectResponse { return ConnectResponse{s.List.Struct(i)} }
func (s ConnectResponse_List) Set(i int, v ConnectResponse) error {
return s.List.SetStruct(i, v.Struct)
}
func (s ConnectResponse_List) String() string {
str, _ := text.MarshalList(0xb1032ec91cef8727, s.List)
return str
}
// ConnectResponse_Promise is a wrapper for a ConnectResponse promised by a client call.
type ConnectResponse_Promise struct{ *capnp.Pipeline }
func (p ConnectResponse_Promise) Struct() (ConnectResponse, error) {
s, err := p.Pipeline.Struct()
return ConnectResponse{s}, err
}
const schema_b29021ef7421cc32 = "x\xda\xb4\x91\xcfk\x13A\x1c\xc5\xdf\x9bI\xba\x1e\xa2" +
"\x9b!\xd5\x8b\x8a\xa4\xf8+ES\xdb(\xa2\xa7\x80\x15" +
"TZ\xcc\x14\xcf\x96u;\x98\x92vw\x92\x9dZ\xf2" +
"\x17x\x15/\xe2\xd1\xbb \x15<\x0b\xa2\xa0\xa2\x07\x11" +
"\xff\x80\xfe\x05=y\xf0\xb42)\xdb@)\x08Bo" +
"\xdfy<\xe6}\xbe\xdfW\xfd\xd5\x16\xb3\xe5\xc7\x04t" +
"\xb5<\x91_x\xbas\xeaKSnA5\x98\xcf}" +
"\xab\xbb\x9d\xfa\xb3\xb7(\x8b\x00\x98}\xf9\x95\xea]\x00" +
"\xa8\xadM0\x8f\xda\x0fK\xafN\xf4?B7\xb8\xdf" +
"\xda\xaa\xf3\x03k7\x18\x00\xb5k|\x03\xe6\x9f\x87?" +
"\xcf\xbf>\xd9\xfc\x04\xd5\x10c3\xd8\xda\xf6\xce?#" +
"\xe7o\xde\x07\xf3\xeb\xdf\x7f\xbc\x7f\xd1\x9b\xdf>\x80\xa0" +
"uT<g\xed\x9c\x1fku\xe1!\xfa\x1b\xab\xf1L" +
"\x16w'\xccz4\xe3\x1f\xcb\xeb\xc6E+\x91\x8b\x96" +
"\xed ui\x9c\xae5\xe3\xc8&\xf6\xe6\xad4IL" +
"\xec\x96Lf\xd3$3@\x87\xd4Gd\x09(\x11P" +
"\x8d9@\x9f\x95\xd4W\x04\x159I/^\xbe\x07\xe8" +
"K\x92\xfa\x8e\xe0\x193\x18\xa4\x03V X\x01\xf3\"" +
"\x06\x00\x8f\x81\x1dIV\xc7\xe8\xa0\x17\xff\x87\xae\xbfa" +
"27b\xab\xec\xb1\xdd\x9e\x06t[R/\x08\x16h" +
"w\xbd6/\xa9;\x82Jp\x92\x02P\x8b\x9ewA" +
"Rw\x05\xc3\x15\x93\xb9\x027tCk\x18\x8e[\x00" +
"\x19\x1e\xda\x16\xabi\xf2`h\xcd\xee\x16#\xb0\xd3\xd3" +
"\xfe3u|\x09\xa0Pj\x0a\x08\xbb\xce\xd9|\xd3<" +
"\xca\xd2\xb8g@\x17\xb8\xd8\xeeE\x95\xff\x19\xb5\xb8\xab" +
"3\xdaW\xe3\xd4A5z\xf1\xa2\xa4\xbe*\x18\xf4\xcc" +
"\xb0\xb8J\xf0$Z+\xe6\xbf\x01\x00\x00\xff\xff\xf5\xed" +
"\xc9\xfe"
func init() {
schemas.Register(schema_b29021ef7421cc32,
0xb1032ec91cef8727,
0xc47116a1045e4061,
0xc52e1bac26d379c8,
0xe1446b97bfd1cd37)
}
+8
View File
@@ -25,6 +25,14 @@ func (f *Filter) ByName(name string) {
f.queryParams.Set("name", name)
}
func (f *Filter) ByNamePrefix(namePrefix string) {
f.queryParams.Set("name_prefix", namePrefix)
}
func (f *Filter) ExcludeNameWithPrefix(excludePrefix string) {
f.queryParams.Set("exclude_prefix", excludePrefix)
}
func (f *Filter) NoDeleted() {
f.queryParams.Set("is_deleted", "false")
}