Compare commits

...

15 Commits

Author SHA1 Message Date
Nuno Diegues 3f4407ce27 Release 2021.10.3 2021-10-14 17:52:50 +01:00
Nuno Diegues d9636c73b4 TUN-5257: Fix more cfsetup targets that were broken by recent package changes 2021-10-14 16:47:34 +01:00
Nuno Diegues 6cbf90883d TUN-5255: Fix potential panic if Cloudflare API fails to respond to GetTunnel(id) during delete command 2021-10-14 15:18:34 +00:00
Nuno Diegues 76ade67f95 Release 2021.10.2 2021-10-14 14:42:56 +01:00
Nuno Diegues 997f2cf612 TUN-5250: Add missing packages for cfsetup to succeed in github release pkgs target 2021-10-14 08:56:21 +00:00
Sudarsan Reddy ceb509ee98 TUN-5138: Switch to QUIC on auto protocol based on threshold 2021-10-14 09:18:20 +01:00
Sudarsan Reddy 5a3c0fdffa Release 2021.10.1 2021-10-13 19:29:24 +01:00
Sudarsan Reddy 2822fbe3db TUN-5249: Revert "TUN-5138: Switch to QUIC on auto protocol based on threshold"
This reverts commit e445fd92f7
2021-10-13 19:06:31 +01:00
Sudarsan Reddy 5148d00516 TUN-5246: Use protocol: quic for Quick tunnels if one is not already set 2021-10-13 08:04:21 +00:00
Sudarsan Reddy bb10e1dee5 Release 2021.10.0 2021-10-12 09:50:49 +01:00
Sudarsan Reddy e445fd92f7 TUN-5138: Switch to QUIC on auto protocol based on threshold 2021-10-11 11:05:20 +00:00
Sudarsan Reddy bccf4a63dc UN-5213: Increase MaxStreams value for QUIC transport
The default max streams value of 100 is rather small when subject to
high load in terms of connecting QUIC with streams faster than it can
create new ones. This high value allows for more throughput.
2021-10-08 13:48:20 +01:00
Sudarsan Reddy 7059ef8e13 TUN-5195: Do not set empty body if not applicable
Go's client defaults to chunked encoding after a 200ms delay if the following cases are true:
  * the request body blocks
  * the content length is not set (or set to -1)
  * the method doesn't usually have a body (GET, HEAD, DELETE, ...)
  * there is no transfer-encoding=chunked already set.
So for non websocket requests, if transfer-encoding isn't chunked and content length is 0, we dont set a request body.
2021-10-07 15:47:27 +01:00
Nuno Diegues cbdf88ea28 TUN-5164: Update README and clean up references to Argo Tunnel (using Cloudflare Tunnel instead) 2021-09-29 08:27:47 +00:00
Sudarsan Reddy 79ebfa8304 TUN-5169: Release 2021.9.2 CHANGES.md 2021-09-29 08:21:22 +00:00
30 changed files with 531 additions and 286 deletions
+3 -3
View File
@@ -46,8 +46,8 @@ git reset --hard origin/master
URL="https://packages.argotunnel.com/dl/cloudflared-$VERSION-darwin-amd64.tgz"
tee cloudflared.rb <<EOF
class Cloudflared < Formula
desc 'Argo Tunnel'
homepage 'https://developers.cloudflare.com/argo-tunnel/'
desc 'Cloudflare Tunnel'
homepage 'https://developers.cloudflare.com/cloudflare-one/connections/connect-apps'
url '$URL'
sha256 '$SHA256'
version '$VERSION'
@@ -62,6 +62,6 @@ git add cloudflared.rb
git diff
git config user.name "cloudflare-warp-bot"
git config user.email "warp-bot@cloudflare.com"
git commit -m "Release Argo Tunnel $VERSION"
git commit -m "Release Cloudflare Tunnel $VERSION"
git push -v origin master
+13
View File
@@ -1,5 +1,18 @@
**Experimental**: This is a new format for release notes. The format and availability is subject to change.
## 2021.9.2
### New features
- `cloudflared` can now run with `quic` as the underlying tunnel transport protocol. To try it, change or add "protocol: quic" to your config.yml file or
run cloudflared with the `--protocol quic` flag. e.g:
`cloudflared tunnel --protocol quic run <tunnel-name>`
### Bug Fixes
- Fixed some generic transport bugs in `quic` mode. It's advised to upgrade to at least this version (2021.9.2) when running `cloudflared`
with `quic` protocol.
- `cloudflared` docker images will now show version.
## 2021.8.4
### Improvements
- Temporary tunnels (those hosted on trycloudflare.com that do not require a Cloudflare login) now run as Named Tunnels
+1 -1
View File
@@ -132,7 +132,7 @@ define build_package
cp cloudflared $(PACKAGE_DIR)/cloudflared
cat cloudflared_man_template | sed -e 's/\$${VERSION}/$(VERSION)/; s/\$${DATE}/$(DATE)/' > $(PACKAGE_DIR)/cloudflared.1
fakeroot fpm -C $(PACKAGE_DIR) -s dir -t $(1) \
--description 'Cloudflare Argo tunnel daemon' \
--description 'Cloudflare Tunnel daemon' \
--vendor 'Cloudflare' \
--license 'Cloudflare Service Agreement' \
--url 'https://github.com/cloudflare/cloudflared' \
+27 -8
View File
@@ -1,14 +1,28 @@
# Argo Tunnel client
# Cloudflare Tunnel client
Contains the command-line client for Cloudflare Tunnel, a tunneling daemon that proxies traffic from the Cloudflare network to your origins.
This daemon sits between Cloudflare network and your origin (e.g. a webserver). Cloudflare attracts client requests and sends them to you
via this daemon, without requiring you to poke holes on your firewall --- your origin can remain as closed as possible.
Extensive documentation can be found in the [Cloudflare Tunnel section](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps) of the Cloudflare Docs.
All usages related with proxying to your origins are available under `cloudflared tunnel help`.
You can also use `cloudflared` to access Tunnel origins (that are protected with `cloudflared tunnel`) for TCP traffic
at Layer 4 (i.e., not HTTP/websocket), which is relevant for use cases such as SSH, RDP, etc.
Such usages are available under `cloudflared access help`.
You can instead use [WARP client](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/private-networks)
to access private origins behind Tunnels for Layer 4 traffic without requiring `cloudflared access` commands on the client side.
Contains the command-line client for Argo Tunnel, a tunneling daemon that proxies any local webserver through the Cloudflare network. Extensive documentation can be found in the [Argo Tunnel section](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps) of the Cloudflare Docs.
## Before you get started
Before you use Argo Tunnel, you'll need to complete a few steps in the Cloudflare dashboard. The website you add to Cloudflare will be used to route traffic to your Tunnel.
Before you use Cloudflare Tunnel, you'll need to complete a few steps in the Cloudflare dashboard: you need to add a
website to your Cloudflare account. Note that today it is possible to use Tunnel without a website (e.g. for private
routing), but for legacy reasons this requirement is still necessary:
1. [Add a website to Cloudflare](https://support.cloudflare.com/hc/en-us/articles/201720164-Creating-a-Cloudflare-account-and-adding-a-website)
2. [Change your domain nameservers to Cloudflare](https://support.cloudflare.com/hc/en-us/articles/205195708)
## 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.
@@ -18,18 +32,23 @@ Downloads are available as standalone binaries, a Docker image, and Debian, RPM,
* 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)
User documentation for Argo Tunnel can be found at https://developers.cloudflare.com/cloudflare-one/connections/connect-apps
User documentation for Cloudflare Tunnel can be found at https://developers.cloudflare.com/cloudflare-one/connections/connect-apps
## Creating Tunnels and routing traffic
Once installed, you can authenticate `cloudflared` into your Cloudflare account and begin creating Tunnels that serve traffic for hostnames in your account.
Once installed, you can authenticate `cloudflared` into your Cloudflare account and begin creating Tunnels to serve traffic to your origins.
* Create a Tunnel with [these instructions](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/create-tunnel)
* Route traffic to that Tunnel with [DNS records in Cloudflare](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/dns) or with a [Cloudflare Load Balancer](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/lb)
* 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)
## TryCloudflare
Want to test Argo Tunnel before adding a website to Cloudflare? You can do so with TryCloudflare using the documentation [available here](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/trycloudflare).
Want to test Cloudflare Tunnel before adding a website to Cloudflare? You can do so with TryCloudflare using the documentation [available here](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/trycloudflare).
## Deprecated versions
+19
View File
@@ -1,3 +1,22 @@
2021.10.3
- 2021-10-14 TUN-5255: Fix potential panic if Cloudflare API fails to respond to GetTunnel(id) during delete command
- 2021-10-14 TUN-5257: Fix more cfsetup targets that were broken by recent package changes
2021.10.2
- 2021-10-11 TUN-5138: Switch to QUIC on auto protocol based on threshold
- 2021-10-14 TUN-5250: Add missing packages for cfsetup to succeed in github release pkgs target
2021.10.1
- 2021-10-12 TUN-5246: Use protocol: quic for Quick tunnels if one is not already set
- 2021-10-13 TUN-5249: Revert "TUN-5138: Switch to QUIC on auto protocol based on threshold"
2021.10.0
- 2021-10-11 TUN-5138: Switch to QUIC on auto protocol based on threshold
- 2021-10-07 TUN-5195: Do not set empty body if not applicable
- 2021-10-08 UN-5213: Increase MaxStreams value for QUIC transport
- 2021-09-28 TUN-5169: Release 2021.9.2 CHANGES.md
- 2021-09-28 TUN-5164: Update README and clean up references to Argo Tunnel (using Cloudflare Tunnel instead)
2021.9.2
- 2021-09-21 TUN-5129: Use go 1.17 and copy .git folder to docker build to compute version
- 2021-09-21 TUN-5128: Enforce maximum grace period
+8
View File
@@ -55,6 +55,8 @@ stretch: &stretch
# libmsi and libgcab are libraries the wixl binary depends on.
- libmsi-dev
- libgcab-dev
- python3-dev
- libffi-dev
- python3-setuptools
- python3-pip
pre-cache:
@@ -111,6 +113,9 @@ stretch: &stretch
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- python3-dev
- libffi-dev
- python3-setuptools
- python3-pip
pre-cache: &install_pygithub
@@ -163,6 +168,9 @@ stretch: &stretch
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- python3-dev
- libffi-dev
- python3-setuptools
- python3-pip
pre-cache: *install_pygithub
+1 -1
View File
@@ -11,7 +11,7 @@ func RemovedCommand(name string) *cli.Command {
Name: name,
Action: func(context *cli.Context) error {
return cli.Exit(
fmt.Sprintf("%s command is no longer supported by cloudflared. Consult Argo Tunnel documentation for possible alternative solutions.", name),
fmt.Sprintf("%s command is no longer supported by cloudflared. Consult Cloudflare Tunnel documentation for possible alternative solutions.", name),
-1,
)
},
+9 -9
View File
@@ -19,11 +19,11 @@ import (
func runApp(app *cli.App, graceShutdownC chan struct{}) {
app.Commands = append(app.Commands, &cli.Command{
Name: "service",
Usage: "Manages the Argo Tunnel system service",
Usage: "Manages the Cloudflare Tunnel system service",
Subcommands: []*cli.Command{
{
Name: "install",
Usage: "Install Argo Tunnel as a system service",
Usage: "Install Cloudflare Tunnel as a system service",
Action: cliutil.ConfiguredAction(installLinuxService),
Flags: []cli.Flag{
&cli.BoolFlag{
@@ -34,7 +34,7 @@ func runApp(app *cli.App, graceShutdownC chan struct{}) {
},
{
Name: "uninstall",
Usage: "Uninstall the Argo Tunnel service",
Usage: "Uninstall the Cloudflare Tunnel service",
Action: cliutil.ConfiguredAction(uninstallLinuxService),
},
},
@@ -55,7 +55,7 @@ var systemdTemplates = []ServiceTemplate{
{
Path: "/etc/systemd/system/cloudflared.service",
Content: `[Unit]
Description=Argo Tunnel
Description=Cloudflare Tunnel
After=network.target
[Service]
@@ -72,7 +72,7 @@ WantedBy=multi-user.target
{
Path: "/etc/systemd/system/cloudflared-update.service",
Content: `[Unit]
Description=Update Argo Tunnel
Description=Update Cloudflare Tunnel
After=network.target
[Service]
@@ -82,7 +82,7 @@ ExecStart=/bin/bash -c '{{ .Path }} update; code=$?; if [ $code -eq 11 ]; then s
{
Path: "/etc/systemd/system/cloudflared-update.timer",
Content: `[Unit]
Description=Update Argo Tunnel
Description=Update Cloudflare Tunnel
[Timer]
OnCalendar=daily
@@ -99,7 +99,7 @@ var sysvTemplate = ServiceTemplate{
Content: `#!/bin/sh
# For RedHat and cousins:
# chkconfig: 2345 99 01
# description: Argo Tunnel agent
# description: Cloudflare Tunnel agent
# processname: {{.Path}}
### BEGIN INIT INFO
# Provides: {{.Path}}
@@ -107,8 +107,8 @@ var sysvTemplate = ServiceTemplate{
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Argo Tunnel
# Description: Argo Tunnel agent
# Short-Description: Cloudflare Tunnel
# Description: Cloudflare Tunnel agent
### END INIT INFO
name=$(basename $(readlink -f $0))
cmd="{{.Path}} --config /etc/cloudflared/config.yml --pidfile /var/run/$name.pid --autoupdate-freq 24h0m0s{{ range .ExtraArgs }} {{ . }}{{ end }}"
+11 -11
View File
@@ -20,16 +20,16 @@ const (
func runApp(app *cli.App, graceShutdownC chan struct{}) {
app.Commands = append(app.Commands, &cli.Command{
Name: "service",
Usage: "Manages the Argo Tunnel launch agent",
Usage: "Manages the Cloudflare Tunnel launch agent",
Subcommands: []*cli.Command{
{
Name: "install",
Usage: "Install Argo Tunnel as an user launch agent",
Usage: "Install Cloudflare Tunnel as an user launch agent",
Action: cliutil.ConfiguredAction(installLaunchd),
},
{
Name: "uninstall",
Usage: "Uninstall the Argo Tunnel launch agent",
Usage: "Uninstall the Cloudflare Tunnel launch agent",
Action: cliutil.ConfiguredAction(uninstallLaunchd),
},
},
@@ -110,13 +110,13 @@ func installLaunchd(c *cli.Context) error {
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
if isRootUser() {
log.Info().Msg("Installing Argo Tunnel client as a system launch daemon. " +
"Argo Tunnel client will run at boot")
log.Info().Msg("Installing Cloudflare Tunnel client as a system launch daemon. " +
"Cloudflare Tunnel client will run at boot")
} else {
log.Info().Msg("Installing Argo Tunnel client as an user launch agent. " +
"Note that Argo Tunnel client will only run when the user is logged in. " +
"If you want to run Argo Tunnel client at boot, install with root permission. " +
"For more information, visit https://developers.cloudflare.com/argo-tunnel/reference/service/")
log.Info().Msg("Installing Cloudflare Tunnel client as an user launch agent. " +
"Note that Cloudflare Tunnel client will only run when the user is logged in. " +
"If you want to run Cloudflare Tunnel client at boot, install with root permission. " +
"For more information, visit https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/run-as-service")
}
etPath, err := os.Executable()
if err != nil {
@@ -159,9 +159,9 @@ func uninstallLaunchd(c *cli.Context) error {
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
if isRootUser() {
log.Info().Msg("Uninstalling Argo Tunnel as a system launch daemon")
log.Info().Msg("Uninstalling Cloudflare Tunnel as a system launch daemon")
} else {
log.Info().Msg("Uninstalling Argo Tunnel as an user launch agent")
log.Info().Msg("Uninstalling Cloudflare Tunnel as an user launch agent")
}
installPath, err := installPath()
if err != nil {
+2 -2
View File
@@ -67,7 +67,7 @@ func main() {
app.Copyright = fmt.Sprintf(
`(c) %d Cloudflare Inc.
Your installation of cloudflared software constitutes a symbol of your signature indicating that you accept
the terms of the Cloudflare License (https://developers.cloudflare.com/argo-tunnel/license/),
the terms of the Cloudflare License (https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/license),
Terms (https://www.cloudflare.com/terms/) and Privacy Policy (https://www.cloudflare.com/privacypolicy/).`,
time.Now().Year(),
)
@@ -76,7 +76,7 @@ func main() {
You can use it to authenticate a session to reach an API behind Access, route web traffic to this machine,
and configure access control.
See https://developers.cloudflare.com/argo-tunnel/ for more in-depth documentation.`
See https://developers.cloudflare.com/cloudflare-one/connections/connect-apps for more in-depth documentation.`
app.Flags = flags()
app.Action = action(graceShutdownC)
app.Commands = commands(cli.ShowVersion)
+4 -4
View File
@@ -126,14 +126,14 @@ func buildTunnelCommand(subcommands []*cli.Command) *cli.Command {
Name: "tunnel",
Action: cliutil.ConfiguredAction(TunnelCommand),
Category: "Tunnel",
Usage: "Make a locally-running web service accessible over the internet using Argo Tunnel.",
Usage: "Make a locally-running web service accessible over the internet using Cloudflare Tunnel.",
ArgsUsage: " ",
Description: `Argo Tunnel asks you to specify a hostname on a Cloudflare-powered
Description: `Cloudflare Tunnel asks you to specify a hostname on a Cloudflare-powered
domain you control and a local address. Traffic from that hostname is routed
(optionally via a Cloudflare Load Balancer) to this machine and appears on the
specified port where it can be served.
This feature requires your Cloudflare account be subscribed to the Argo Smart Routing feature.
This feature requires your Cloudflare account be subscribed to the Cloudflare Smart Routing feature.
To use, begin by calling login to download a certificate:
@@ -488,7 +488,7 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
credentialsFileFlag,
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "is-autoupdated",
Usage: "Signal the new process that Argo Tunnel connector has been autoupdated",
Usage: "Signal the new process that Cloudflare Tunnel connector has been autoupdated",
Value: false,
Hidden: true,
}),
+1 -1
View File
@@ -238,7 +238,7 @@ func prepareTunnelConfig(
log.Info().Msgf("Warp-routing is enabled")
}
protocolSelector, err := connection.NewProtocolSelector(c.String("protocol"), warpRoutingEnabled, namedTunnel, edgediscovery.HTTP2Percentage, origin.ResolveTTL, log)
protocolSelector, err := connection.NewProtocolSelector(c.String("protocol"), warpRoutingEnabled, namedTunnel, edgediscovery.ProtocolPercentage, origin.ResolveTTL, log)
if err != nil {
return nil, ingress.Ingress{}, err
}
+4
View File
@@ -70,6 +70,10 @@ func RunQuickTunnel(sc *subcommandContext) error {
sc.log.Info().Msg(line)
}
if !sc.c.IsSet("protocol") {
sc.c.Set("protocol", "quic")
}
return StartServer(
sc.c,
version,
+2 -2
View File
@@ -151,7 +151,7 @@ func (sc *subcommandContext) readTunnelCredentials(credFinder CredFinder) (conne
func (sc *subcommandContext) create(name string, credentialsFilePath string) (*tunnelstore.Tunnel, error) {
client, err := sc.client()
if err != nil {
return nil, errors.Wrap(err, "couldn't create client to talk to Argo Tunnel backend")
return nil, errors.Wrap(err, "couldn't create client to talk to Cloudflare Tunnel backend")
}
tunnelSecret, err := generateTunnelSecret()
@@ -230,7 +230,7 @@ func (sc *subcommandContext) delete(tunnelIDs []uuid.UUID) error {
for _, id := range tunnelIDs {
tunnel, err := client.GetTunnel(id)
if err != nil {
return errors.Wrapf(err, "Can't get tunnel information. Please check tunnel id: %s", tunnel.ID)
return errors.Wrapf(err, "Can't get tunnel information. Please check tunnel id: %s", id)
}
// Check if tunnel DeletedAt field has already been set
+1 -1
View File
@@ -19,7 +19,7 @@ import (
const (
DefaultCheckUpdateFreq = time.Hour * 24
noUpdateInShellMessage = "cloudflared will not automatically update when run from the shell. To enable auto-updates, run cloudflared as a service: https://developers.cloudflare.com/argo-tunnel/reference/service/"
noUpdateInShellMessage = "cloudflared will not automatically update when run from the shell. To enable auto-updates, run cloudflared as a service: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/run-as-service"
noUpdateOnWindowsMessage = "cloudflared will not automatically update on Windows systems."
noUpdateManagedPackageMessage = "cloudflared will not automatically update if installed by a package manager."
isManagedInstallFile = ".installedFromPackageManager"
+9 -9
View File
@@ -25,8 +25,8 @@ import (
const (
windowsServiceName = "Cloudflared"
windowsServiceDescription = "Argo Tunnel agent"
windowsServiceUrl = "https://developers.cloudflare.com/argo-tunnel/reference/service/"
windowsServiceDescription = "Cloudflare Tunnel agent"
windowsServiceUrl = "https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/run-as-service#windows"
recoverActionDelay = time.Second * 20
failureCountResetPeriod = time.Hour * 24
@@ -45,16 +45,16 @@ const (
func runApp(app *cli.App, graceShutdownC chan struct{}) {
app.Commands = append(app.Commands, &cli.Command{
Name: "service",
Usage: "Manages the Argo Tunnel Windows service",
Usage: "Manages the Cloudflare Tunnel Windows service",
Subcommands: []*cli.Command{
{
Name: "install",
Usage: "Install Argo Tunnel as a Windows service",
Usage: "Install Cloudflare Tunnel as a Windows service",
Action: cliutil.ConfiguredAction(installWindowsService),
},
{
Name: "uninstall",
Usage: "Uninstall the Argo Tunnel service",
Usage: "Uninstall the Cloudflare Tunnel service",
Action: cliutil.ConfiguredAction(uninstallWindowsService),
},
},
@@ -176,7 +176,7 @@ func (s *windowsService) Execute(serviceArgs []string, r <-chan svc.ChangeReques
func installWindowsService(c *cli.Context) error {
zeroLogger := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
zeroLogger.Info().Msg("Installing Argo Tunnel Windows service")
zeroLogger.Info().Msg("Installing Cloudflare Tunnel Windows service")
exepath, err := os.Executable()
if err != nil {
return errors.Wrap(err, "Cannot find path name that start the process")
@@ -198,7 +198,7 @@ func installWindowsService(c *cli.Context) error {
return errors.Wrap(err, "Cannot install service")
}
defer s.Close()
log.Info().Msg("Argo Tunnel agent service is installed")
log.Info().Msg("Cloudflare Tunnel agent service is installed")
err = eventlog.InstallAsEventCreate(windowsServiceName, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
s.Delete()
@@ -217,7 +217,7 @@ func uninstallWindowsService(c *cli.Context) error {
With().
Str(LogFieldWindowsServiceName, windowsServiceName).Logger()
log.Info().Msg("Uninstalling Argo Tunnel Windows Service")
log.Info().Msg("Uninstalling Cloudflare Tunnel Windows Service")
m, err := mgr.Connect()
if err != nil {
return errors.Wrap(err, "Cannot establish a connection to the service control manager")
@@ -232,7 +232,7 @@ func uninstallWindowsService(c *cli.Context) error {
if err != nil {
return errors.Wrap(err, "Cannot delete service")
}
log.Info().Msg("Argo Tunnel agent service is uninstalled")
log.Info().Msg("Cloudflare Tunnel agent service is uninstalled")
err = eventlog.Remove(windowsServiceName)
if err != nil {
return errors.Wrap(err, "Cannot remove event logger")
+2
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"math"
"net/http"
"strconv"
"strings"
@@ -19,6 +20,7 @@ const (
lbProbeUserAgentPrefix = "Mozilla/5.0 (compatible; Cloudflare-Traffic-Manager/1.0; +https://www.cloudflare.com/traffic-manager/;"
LogFieldConnIndex = "connIndex"
MaxGracePeriod = time.Minute * 3
MaxConcurrentStreams = math.MaxUint32
)
var switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols, http.StatusText(http.StatusSwitchingProtocols))
+1 -2
View File
@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"io"
"math"
"net"
"net/http"
"runtime/debug"
@@ -60,7 +59,7 @@ func NewHTTP2Connection(
return &HTTP2Connection{
conn: conn,
server: &http2.Server{
MaxConcurrentStreams: math.MaxUint32,
MaxConcurrentStreams: MaxConcurrentStreams,
},
config: config,
connOptions: connOptions,
+105 -53
View File
@@ -7,6 +7,8 @@ import (
"time"
"github.com/rs/zerolog"
"github.com/cloudflare/cloudflared/edgediscovery"
)
const (
@@ -24,7 +26,7 @@ const (
var (
// ProtocolList represents a list of supported protocols for communication with the edge.
ProtocolList = []Protocol{H2mux, HTTP2, QUIC}
ProtocolList = []Protocol{H2mux, HTTP2, HTTP2Warp, QUIC, QUICWarp}
)
type Protocol int64
@@ -36,6 +38,12 @@ const (
HTTP2
// QUIC is used only with named tunnels.
QUIC
// HTTP2Warp is used only with named tunnels. It's useful for warp-routing where we don't want to fallback to
// H2mux on HTTP2 failure to connect.
HTTP2Warp
//QUICWarp is used only with named tunnels. It's useful for warp-routing where we want to fallback to HTTP2 but
// dont' want HTTP2 to fallback to H2mux
QUICWarp
)
// Fallback returns the fallback protocol and whether the protocol has a fallback
@@ -45,8 +53,12 @@ func (p Protocol) fallback() (Protocol, bool) {
return 0, false
case HTTP2:
return H2mux, true
case HTTP2Warp:
return 0, false
case QUIC:
return HTTP2, true
case QUICWarp:
return HTTP2Warp, true
default:
return 0, false
}
@@ -56,9 +68,9 @@ func (p Protocol) String() string {
switch p {
case H2mux:
return "h2mux"
case HTTP2:
case HTTP2, HTTP2Warp:
return "http2"
case QUIC:
case QUIC, QUICWarp:
return "quic"
default:
return fmt.Sprintf("unknown protocol")
@@ -71,11 +83,11 @@ func (p Protocol) TLSSettings() *TLSSettings {
return &TLSSettings{
ServerName: edgeH2muxTLSServerName,
}
case HTTP2:
case HTTP2, HTTP2Warp:
return &TLSSettings{
ServerName: edgeH2TLSServerName,
}
case QUIC:
case QUIC, QUICWarp:
return &TLSSettings{
ServerName: edgeQUICServerName,
NextProtos: []string{"argotunnel"},
@@ -108,29 +120,36 @@ func (s *staticProtocolSelector) Fallback() (Protocol, bool) {
}
type autoProtocolSelector struct {
lock sync.RWMutex
current Protocol
switchThrehold int32
fetchFunc PercentageFetcher
refreshAfter time.Time
ttl time.Duration
log *zerolog.Logger
lock sync.RWMutex
current Protocol
// protocolPool is desired protocols in the order of priority they should be picked in.
protocolPool []Protocol
switchThreshold int32
fetchFunc PercentageFetcher
refreshAfter time.Time
ttl time.Duration
log *zerolog.Logger
}
func newAutoProtocolSelector(
current Protocol,
switchThrehold int32,
protocolPool []Protocol,
switchThreshold int32,
fetchFunc PercentageFetcher,
ttl time.Duration,
log *zerolog.Logger,
) *autoProtocolSelector {
return &autoProtocolSelector{
current: current,
switchThrehold: switchThrehold,
fetchFunc: fetchFunc,
refreshAfter: time.Now().Add(ttl),
ttl: ttl,
log: log,
current: current,
protocolPool: protocolPool,
switchThreshold: switchThreshold,
fetchFunc: fetchFunc,
refreshAfter: time.Now().Add(ttl),
ttl: ttl,
log: log,
}
}
@@ -141,28 +160,39 @@ func (s *autoProtocolSelector) Current() Protocol {
return s.current
}
percentage, err := s.fetchFunc()
protocol, err := getProtocol(s.protocolPool, s.fetchFunc, s.switchThreshold)
if err != nil {
s.log.Err(err).Msg("Failed to refresh protocol")
return s.current
}
s.current = protocol
if s.switchThrehold < percentage {
s.current = HTTP2
} else {
s.current = H2mux
}
s.refreshAfter = time.Now().Add(s.ttl)
return s.current
}
func getProtocol(protocolPool []Protocol, fetchFunc PercentageFetcher, switchThreshold int32) (Protocol, error) {
protocolPercentages, err := fetchFunc()
if err != nil {
return 0, err
}
for _, protocol := range protocolPool {
protocolPercentage := protocolPercentages.GetPercentage(protocol.String())
if protocolPercentage > switchThreshold {
return protocol, nil
}
}
return protocolPool[len(protocolPool)-1], nil
}
func (s *autoProtocolSelector) Fallback() (Protocol, bool) {
s.lock.RLock()
defer s.lock.RUnlock()
return s.current.fallback()
}
type PercentageFetcher func() (int32, error)
type PercentageFetcher func() (edgediscovery.ProtocolPercents, error)
func NewProtocolSelector(
protocolFlag string,
@@ -179,22 +209,34 @@ func NewProtocolSelector(
}, nil
}
// warp routing cannot be served over h2mux connections
if warpRoutingEnabled {
if protocolFlag == H2mux.String() {
log.Warn().Msg("Warp routing is not supported in h2mux protocol. Upgrading to http2 to allow it.")
}
if protocolFlag == QUIC.String() {
return &staticProtocolSelector{
current: QUIC,
}, nil
}
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.")
return &staticProtocolSelector{
current: HTTP2,
}, nil
}
if warpRoutingEnabled {
if protocolFlag == H2mux.String() || fetchedProtocol == H2mux {
log.Warn().Msg("Warp routing is not supported in h2mux protocol. Upgrading to http2 to allow it.")
protocolFlag = HTTP2.String()
fetchedProtocol = HTTP2Warp
}
return selectWarpRoutingProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol)
}
return selectNamedTunnelProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol)
}
func selectNamedTunnelProtocols(
protocolFlag string,
fetchFunc PercentageFetcher,
ttl time.Duration,
log *zerolog.Logger,
threshold int32,
protocol Protocol,
) (ProtocolSelector, error) {
if protocolFlag == H2mux.String() {
return &staticProtocolSelector{
current: H2mux,
@@ -202,31 +244,41 @@ func NewProtocolSelector(
}
if protocolFlag == QUIC.String() {
return newAutoProtocolSelector(QUIC, explicitHTTP2FallbackThreshold, fetchFunc, ttl, log), nil
return newAutoProtocolSelector(QUIC, []Protocol{QUIC, HTTP2, H2mux}, explicitHTTP2FallbackThreshold, fetchFunc, ttl, log), nil
}
http2Percentage, err := fetchFunc()
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.")
return &staticProtocolSelector{
current: HTTP2,
}, nil
}
if protocolFlag == HTTP2.String() {
if http2Percentage < 0 {
return newAutoProtocolSelector(H2mux, explicitHTTP2FallbackThreshold, fetchFunc, ttl, log), nil
}
return newAutoProtocolSelector(HTTP2, explicitHTTP2FallbackThreshold, fetchFunc, ttl, log), nil
return newAutoProtocolSelector(HTTP2, []Protocol{HTTP2, H2mux}, explicitHTTP2FallbackThreshold, fetchFunc, ttl, log), nil
}
if protocolFlag != autoSelectFlag {
return nil, fmt.Errorf("Unknown protocol %s, %s", protocolFlag, AvailableProtocolFlagMessage)
}
threshold := switchThreshold(namedTunnel.Credentials.AccountTag)
if threshold < http2Percentage {
return newAutoProtocolSelector(HTTP2, threshold, fetchFunc, ttl, log), nil
return newAutoProtocolSelector(protocol, []Protocol{QUIC, HTTP2, H2mux}, threshold, fetchFunc, ttl, log), nil
}
func selectWarpRoutingProtocols(
protocolFlag string,
fetchFunc PercentageFetcher,
ttl time.Duration,
log *zerolog.Logger,
threshold int32,
protocol Protocol,
) (ProtocolSelector, error) {
if protocolFlag == QUIC.String() {
return newAutoProtocolSelector(QUICWarp, []Protocol{QUICWarp, HTTP2Warp}, explicitHTTP2FallbackThreshold, fetchFunc, ttl, log), nil
}
return newAutoProtocolSelector(H2mux, threshold, fetchFunc, ttl, log), nil
if protocolFlag == HTTP2.String() {
return newAutoProtocolSelector(HTTP2Warp, []Protocol{HTTP2Warp}, explicitHTTP2FallbackThreshold, fetchFunc, ttl, log), nil
}
if protocolFlag != autoSelectFlag {
return nil, fmt.Errorf("Unknown protocol %s, %s", protocolFlag, AvailableProtocolFlagMessage)
}
return newAutoProtocolSelector(protocol, []Protocol{QUICWarp, HTTP2Warp}, threshold, fetchFunc, ttl, log), nil
}
func switchThreshold(accountTag string) int32 {
+106 -61
View File
@@ -6,6 +6,8 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/cloudflare/cloudflared/edgediscovery"
)
const (
@@ -21,29 +23,23 @@ var (
}
)
func mockFetcher(percentage int32) PercentageFetcher {
return func() (int32, error) {
return percentage, nil
}
}
func mockFetcherWithError() PercentageFetcher {
return func() (int32, error) {
return 0, fmt.Errorf("failed to fetch precentage")
func mockFetcher(getError bool, protocolPercent ...edgediscovery.ProtocolPercent) PercentageFetcher {
return func() (edgediscovery.ProtocolPercents, error) {
if getError {
return nil, fmt.Errorf("failed to fetch precentage")
}
return protocolPercent, nil
}
}
type dynamicMockFetcher struct {
percentage int32
err error
protocolPercents edgediscovery.ProtocolPercents
err error
}
func (dmf *dynamicMockFetcher) fetch() PercentageFetcher {
return func() (int32, error) {
if dmf.err != nil {
return 0, dmf.err
}
return dmf.percentage, nil
return func() (edgediscovery.ProtocolPercents, error) {
return dmf.protocolPercents, dmf.err
}
}
@@ -69,6 +65,7 @@ func TestNewProtocolSelector(t *testing.T) {
name: "named tunnel over h2mux",
protocol: "h2mux",
expectedProtocol: H2mux,
fetchFunc: func() (edgediscovery.ProtocolPercents, error) { return nil, nil },
namedTunnelConfig: testNamedTunnelConfig,
},
{
@@ -77,28 +74,38 @@ func TestNewProtocolSelector(t *testing.T) {
expectedProtocol: HTTP2,
hasFallback: true,
expectedFallback: H2mux,
fetchFunc: mockFetcher(0),
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 0}),
namedTunnelConfig: testNamedTunnelConfig,
},
{
name: "named tunnel http2 disabled",
protocol: "http2",
expectedProtocol: H2mux,
fetchFunc: mockFetcher(-1),
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}),
namedTunnelConfig: testNamedTunnelConfig,
},
{
name: "named tunnel quic disabled",
protocol: "quic",
expectedProtocol: HTTP2,
// Hasfallback true is because if http2 fails, then we further fallback to h2mux.
hasFallback: true,
expectedFallback: H2mux,
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}, edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: -1}),
namedTunnelConfig: testNamedTunnelConfig,
},
{
name: "named tunnel auto all http2 disabled",
protocol: "auto",
expectedProtocol: H2mux,
fetchFunc: mockFetcher(-1),
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}),
namedTunnelConfig: testNamedTunnelConfig,
},
{
name: "named tunnel auto to h2mux",
protocol: "auto",
expectedProtocol: H2mux,
fetchFunc: mockFetcher(0),
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 0}),
namedTunnelConfig: testNamedTunnelConfig,
},
{
@@ -107,36 +114,71 @@ func TestNewProtocolSelector(t *testing.T) {
expectedProtocol: HTTP2,
hasFallback: true,
expectedFallback: H2mux,
fetchFunc: mockFetcher(100),
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}),
namedTunnelConfig: testNamedTunnelConfig,
},
{
name: "named tunnel auto to quic",
protocol: "auto",
expectedProtocol: QUIC,
hasFallback: true,
expectedFallback: HTTP2,
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}),
namedTunnelConfig: testNamedTunnelConfig,
},
{
name: "warp routing requesting h2mux",
protocol: "h2mux",
expectedProtocol: HTTP2,
expectedProtocol: HTTP2Warp,
hasFallback: false,
expectedFallback: H2mux,
fetchFunc: mockFetcher(100),
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}),
warpRoutingEnabled: true,
namedTunnelConfig: testNamedTunnelConfig,
},
{
name: "warp routing requesting h2mux picks HTTP2 even if http2 percent is -1",
protocol: "h2mux",
expectedProtocol: HTTP2Warp,
hasFallback: false,
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}),
warpRoutingEnabled: true,
namedTunnelConfig: testNamedTunnelConfig,
},
{
name: "warp routing http2",
protocol: "http2",
expectedProtocol: HTTP2,
expectedProtocol: HTTP2Warp,
hasFallback: false,
expectedFallback: H2mux,
fetchFunc: mockFetcher(100),
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}),
warpRoutingEnabled: true,
namedTunnelConfig: testNamedTunnelConfig,
},
{
name: "warp routing quic",
protocol: "quic",
expectedProtocol: QUICWarp,
hasFallback: true,
expectedFallback: HTTP2Warp,
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}),
warpRoutingEnabled: true,
namedTunnelConfig: testNamedTunnelConfig,
},
{
name: "warp routing auto",
protocol: "auto",
expectedProtocol: HTTP2,
expectedProtocol: HTTP2Warp,
hasFallback: false,
expectedFallback: H2mux,
fetchFunc: mockFetcher(100),
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}),
warpRoutingEnabled: true,
namedTunnelConfig: testNamedTunnelConfig,
},
{
name: "warp routing auto- quic",
protocol: "auto",
expectedProtocol: QUICWarp,
hasFallback: true,
expectedFallback: HTTP2Warp,
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}, edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}),
warpRoutingEnabled: true,
namedTunnelConfig: testNamedTunnelConfig,
},
@@ -149,14 +191,14 @@ func TestNewProtocolSelector(t *testing.T) {
{
name: "named tunnel unknown protocol",
protocol: "unknown",
fetchFunc: mockFetcher(100),
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}),
namedTunnelConfig: testNamedTunnelConfig,
wantErr: true,
},
{
name: "named tunnel fetch error",
protocol: "unknown",
fetchFunc: mockFetcherWithError(),
protocol: "auto",
fetchFunc: mockFetcher(true),
namedTunnelConfig: testNamedTunnelConfig,
expectedProtocol: HTTP2,
wantErr: false,
@@ -164,18 +206,20 @@ func TestNewProtocolSelector(t *testing.T) {
}
for _, test := range tests {
selector, err := NewProtocolSelector(test.protocol, test.warpRoutingEnabled, test.namedTunnelConfig, test.fetchFunc, testNoTTL, &log)
if test.wantErr {
assert.Error(t, err, fmt.Sprintf("test %s failed", test.name))
} else {
assert.NoError(t, err, fmt.Sprintf("test %s failed", test.name))
assert.Equal(t, test.expectedProtocol, selector.Current(), fmt.Sprintf("test %s failed", test.name))
fallback, ok := selector.Fallback()
assert.Equal(t, test.hasFallback, ok, fmt.Sprintf("test %s failed", test.name))
if test.hasFallback {
assert.Equal(t, test.expectedFallback, fallback, fmt.Sprintf("test %s failed", test.name))
t.Run(test.name, func(t *testing.T) {
selector, err := NewProtocolSelector(test.protocol, test.warpRoutingEnabled, test.namedTunnelConfig, test.fetchFunc, testNoTTL, &log)
if test.wantErr {
assert.Error(t, err, fmt.Sprintf("test %s failed", test.name))
} else {
assert.NoError(t, err, fmt.Sprintf("test %s failed", test.name))
assert.Equal(t, test.expectedProtocol, selector.Current(), fmt.Sprintf("test %s failed", test.name))
fallback, ok := selector.Fallback()
assert.Equal(t, test.hasFallback, ok, fmt.Sprintf("test %s failed", test.name))
if test.hasFallback {
assert.Equal(t, test.expectedFallback, fallback, fmt.Sprintf("test %s failed", test.name))
}
}
}
})
}
}
@@ -185,27 +229,27 @@ func TestAutoProtocolSelectorRefresh(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, H2mux, selector.Current())
fetcher.percentage = 100
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}}
assert.Equal(t, HTTP2, selector.Current())
fetcher.percentage = 0
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 0}}
assert.Equal(t, H2mux, selector.Current())
fetcher.percentage = 100
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}}
assert.Equal(t, HTTP2, selector.Current())
fetcher.err = fmt.Errorf("failed to fetch")
assert.Equal(t, HTTP2, selector.Current())
fetcher.percentage = -1
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}}
fetcher.err = nil
assert.Equal(t, H2mux, selector.Current())
fetcher.percentage = 0
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 0}}
assert.Equal(t, H2mux, selector.Current())
fetcher.percentage = 100
assert.Equal(t, HTTP2, selector.Current())
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}}
assert.Equal(t, QUIC, selector.Current())
}
func TestHTTP2ProtocolSelectorRefresh(t *testing.T) {
@@ -214,35 +258,36 @@ func TestHTTP2ProtocolSelectorRefresh(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, HTTP2, selector.Current())
fetcher.percentage = 100
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}}
assert.Equal(t, HTTP2, selector.Current())
fetcher.percentage = 0
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 0}}
assert.Equal(t, HTTP2, selector.Current())
fetcher.err = fmt.Errorf("failed to fetch")
assert.Equal(t, HTTP2, selector.Current())
fetcher.percentage = -1
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}}
fetcher.err = nil
assert.Equal(t, H2mux, selector.Current())
fetcher.percentage = 0
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 0}}
assert.Equal(t, HTTP2, selector.Current())
fetcher.percentage = 100
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}}
assert.Equal(t, HTTP2, selector.Current())
fetcher.percentage = -1
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}}
assert.Equal(t, H2mux, selector.Current())
}
func TestProtocolSelectorRefreshTTL(t *testing.T) {
fetcher := dynamicMockFetcher{percentage: 100}
fetcher := dynamicMockFetcher{}
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}}
selector, err := NewProtocolSelector("auto", noWarpRoutingEnabled, testNamedTunnelConfig, fetcher.fetch(), time.Hour, &log)
assert.NoError(t, err)
assert.Equal(t, HTTP2, selector.Current())
assert.Equal(t, QUIC, selector.Current())
fetcher.percentage = 0
assert.Equal(t, HTTP2, selector.Current())
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 0}}
assert.Equal(t, QUIC, selector.Current())
}
+19 -1
View File
@@ -158,11 +158,12 @@ func (hrw httpResponseAdapter) WriteErrorResponse(err error) {
quicpogs.WriteConnectResponseData(hrw, err, quicpogs.Metadata{Key: "HttpStatus", Val: strconv.Itoa(http.StatusBadGateway)})
}
func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.Reader) (*http.Request, error) {
func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.ReadCloser) (*http.Request, 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)
if err != nil {
@@ -186,6 +187,16 @@ func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.Reader) (
if err := setContentLength(req); err != nil {
return nil, fmt.Errorf("Error setting content-length: %w", err)
}
// Go's client defaults to chunked encoding after a 200ms delay if the following cases are true:
// * the request body blocks
// * the content length is not set (or set to -1)
// * the method doesn't usually have a body (GET, HEAD, DELETE, ...)
// * there is no transfer-encoding=chunked already set.
// So, if transfer cannot be chunked and content length is 0, we dont set a request body.
if !isWebsocket && !isTransferEncodingChunked(req) && req.ContentLength == 0 {
req.Body = nil
}
stripWebsocketUpgradeHeader(req)
return req, err
}
@@ -197,3 +208,10 @@ func setContentLength(req *http.Request) error {
}
return err
}
func isTransferEncodingChunked(req *http.Request) bool {
transferEncodingVal := req.Header.Get("Transfer-Encoding")
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding suggests that this can be a comma
// separated value as well.
return strings.Contains(strings.ToLower(transferEncodingVal), "chunked")
}
+132 -1
View File
@@ -108,6 +108,10 @@ func TestQUICServer(t *testing.T) {
Key: "HttpMethod",
Val: "POST",
},
quicpogs.Metadata{
Key: "HttpHeader:Content-Length",
Val: "24",
},
},
message: []byte("This is the message body"),
expectedResponse: []byte("This is the message body"),
@@ -297,6 +301,7 @@ func TestBuildHTTPRequest(t *testing.T) {
var tests = []struct {
name string
connectRequest *quicpogs.ConnectRequest
body io.ReadCloser
req *http.Request
}{
{
@@ -341,7 +346,9 @@ func TestBuildHTTPRequest(t *testing.T) {
},
ContentLength: 514,
Host: "cf.host",
Body: io.NopCloser(&bytes.Buffer{}),
},
body: io.NopCloser(&bytes.Buffer{}),
},
{
name: "if content length isn't part of request headers, then it's not set",
@@ -380,13 +387,137 @@ func TestBuildHTTPRequest(t *testing.T) {
},
ContentLength: 0,
Host: "cf.host",
Body: nil,
},
body: io.NopCloser(&bytes.Buffer{}),
},
{
name: "if content length is 0, but transfer-encoding is chunked, body is not nil",
connectRequest: &quicpogs.ConnectRequest{
Dest: "http://test.com",
Metadata: []quicpogs.Metadata{
quicpogs.Metadata{
Key: "HttpHeader:Another-Header",
Val: "Misc",
},
quicpogs.Metadata{
Key: "HttpHeader:Transfer-Encoding",
Val: "chunked",
},
quicpogs.Metadata{
Key: "HttpHost",
Val: "cf.host",
},
quicpogs.Metadata{
Key: "HttpMethod",
Val: "get",
},
},
},
req: &http.Request{
Method: "get",
URL: &url.URL{
Scheme: "http",
Host: "test.com",
},
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header{
"Another-Header": []string{"Misc"},
"Transfer-Encoding": []string{"chunked"},
},
ContentLength: 0,
Host: "cf.host",
Body: io.NopCloser(&bytes.Buffer{}),
},
body: io.NopCloser(&bytes.Buffer{}),
},
{
name: "if content length is 0, but transfer-encoding is gzip,chunked, body is not nil",
connectRequest: &quicpogs.ConnectRequest{
Dest: "http://test.com",
Metadata: []quicpogs.Metadata{
quicpogs.Metadata{
Key: "HttpHeader:Another-Header",
Val: "Misc",
},
quicpogs.Metadata{
Key: "HttpHeader:Transfer-Encoding",
Val: "gzip,chunked",
},
quicpogs.Metadata{
Key: "HttpHost",
Val: "cf.host",
},
quicpogs.Metadata{
Key: "HttpMethod",
Val: "get",
},
},
},
req: &http.Request{
Method: "get",
URL: &url.URL{
Scheme: "http",
Host: "test.com",
},
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header{
"Another-Header": []string{"Misc"},
"Transfer-Encoding": []string{"gzip,chunked"},
},
ContentLength: 0,
Host: "cf.host",
Body: io.NopCloser(&bytes.Buffer{}),
},
body: io.NopCloser(&bytes.Buffer{}),
},
{
name: "if content length is 0, and connect request is a websocket, body is not nil",
connectRequest: &quicpogs.ConnectRequest{
Type: quicpogs.ConnectionTypeWebsocket,
Dest: "http://test.com",
Metadata: []quicpogs.Metadata{
quicpogs.Metadata{
Key: "HttpHeader:Another-Header",
Val: "Misc",
},
quicpogs.Metadata{
Key: "HttpHost",
Val: "cf.host",
},
quicpogs.Metadata{
Key: "HttpMethod",
Val: "get",
},
},
},
req: &http.Request{
Method: "get",
URL: &url.URL{
Scheme: "http",
Host: "test.com",
},
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header{
"Another-Header": []string{"Misc"},
},
ContentLength: 0,
Host: "cf.host",
Body: io.NopCloser(&bytes.Buffer{}),
},
body: io.NopCloser(&bytes.Buffer{}),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req, err := buildHTTPRequest(test.connectRequest, nil)
req, err := buildHTTPRequest(test.connectRequest, test.body)
assert.NoError(t, err)
test.req = test.req.WithContext(req.Context())
assert.Equal(t, test.req, req)
+28 -23
View File
@@ -1,45 +1,50 @@
package edgediscovery
import (
"encoding/json"
"fmt"
"net"
"strconv"
"strings"
)
const (
protocolRecord = "protocol.argotunnel.com"
protocolRecord = "protocol-v2.argotunnel.com"
)
var (
errNoProtocolRecord = fmt.Errorf("No TXT record found for %s to determine connection protocol", protocolRecord)
)
func HTTP2Percentage() (int32, error) {
// ProtocolPercent represents a single Protocol Percentage combination.
type ProtocolPercent struct {
Protocol string `json:"protocol"`
Percentage int32 `json:"percentage"`
}
// ProtocolPercents represents the preferred distribution ratio of protocols when protocol isn't specified.
type ProtocolPercents []ProtocolPercent
// GetPercentage returns the threshold percentage of a single protocol.
func (p ProtocolPercents) GetPercentage(protocol string) int32 {
for _, protocolPercent := range p {
if strings.ToLower(protocolPercent.Protocol) == strings.ToLower(protocol) {
return protocolPercent.Percentage
}
}
return 0
}
// ProtocolPercentage returns the ratio of protocols and a specification ratio for their selection.
func ProtocolPercentage() (ProtocolPercents, error) {
records, err := net.LookupTXT(protocolRecord)
if err != nil {
return 0, err
return nil, err
}
if len(records) == 0 {
return 0, errNoProtocolRecord
return nil, errNoProtocolRecord
}
return parseHTTP2Precentage(records[0])
}
// The record looks like http2=percentage
func parseHTTP2Precentage(record string) (int32, error) {
const key = "http2"
slices := strings.Split(record, "=")
if len(slices) != 2 {
return 0, fmt.Errorf("Malformed TXT record %s, expect http2=percentage", record)
}
if slices[0] != key {
return 0, fmt.Errorf("Incorrect key %s, expect %s", slices[0], key)
}
percentage, err := strconv.ParseInt(slices[1], 10, 32)
if err != nil {
return 0, err
}
return int32(percentage), nil
var protocolsWithPercent ProtocolPercents
err = json.Unmarshal([]byte(records[0]), &protocolsWithPercent)
return protocolsWithPercent, err
}
+2 -70
View File
@@ -6,75 +6,7 @@ import (
"github.com/stretchr/testify/assert"
)
func TestHTTP2Percentage(t *testing.T) {
_, err := HTTP2Percentage()
func TestProtocolPercentage(t *testing.T) {
_, err := ProtocolPercentage()
assert.NoError(t, err)
}
func TestParseHTTP2Precentage(t *testing.T) {
tests := []struct {
record string
percentage int32
wantErr bool
}{
{
record: "http2=-1",
percentage: -1,
wantErr: false,
},
{
record: "http2=0",
percentage: 0,
wantErr: false,
},
{
record: "http2=50",
percentage: 50,
wantErr: false,
},
{
record: "http2=100",
percentage: 100,
wantErr: false,
},
{
record: "http2=1000",
percentage: 1000,
wantErr: false,
},
{
record: "http2=10.5",
wantErr: true,
},
{
record: "http2=10 h2mux=90",
wantErr: true,
},
{
record: "http2=ten",
wantErr: true,
},
{
record: "h2mux=100",
wantErr: true,
},
{
record: "http2",
wantErr: true,
},
{
record: "http2=",
wantErr: true,
},
}
for _, test := range tests {
p, err := parseHTTP2Precentage(test.record)
if test.wantErr {
assert.Error(t, err)
} else {
assert.Equal(t, test.percentage, p)
}
}
}
+5 -5
View File
@@ -37,7 +37,7 @@ type OriginUpTime struct {
UpTime string `json:"uptime"`
}
const defaultServerName = "the Argo Tunnel test server"
const defaultServerName = "the Cloudflare Tunnel test server"
const indexTemplate = `
<!DOCTYPE html>
<html lang="en">
@@ -45,10 +45,10 @@ const indexTemplate = `
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<title>
Argo Tunnel Connection
Cloudflare Tunnel Connection
</title>
<meta name="author" content="">
<meta name="description" content="Argo Tunnel Connection">
<meta name="description" content="Cloudflare Tunnel Connection">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}section{display:block}h1{font-size:2em;margin:.67em 0}a{background-color:transparent;-webkit-text-decoration-skip:objects}/* 1 */::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}/* 1 */a,body,dd,div,dl,dt,h1,h4,html,p,section{box-sizing:border-box}.bt{border-top-style:solid;border-top-width:1px}.bl{border-left-style:solid;border-left-width:1px}.b--orange{border-color:#f38020}.br1{border-radius:.125rem}.bw2{border-width:.25rem}.dib{display:inline-block}.sans-serif{font-family:open sans,-apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica neue,helvetica,ubuntu,roboto,noto,segoe ui,arial,sans-serif}.overflow-x-auto{overflow-x:auto}.code{font-family:Consolas,monaco,monospace}.b{font-weight:700}.fw3{font-weight:300}.fw4{font-weight:400}.fw5{font-weight:500}.fw6{font-weight:600}.lh-copy{line-height:1.5}.link{text-decoration:none}.link,.link:active,.link:focus,.link:hover,.link:link,.link:visited{transition:color .15s ease-in}.link:focus{outline:1px dotted currentColor}.mw-100{max-width:100%}.mw4{max-width:8rem}.mw7{max-width:48rem}.bg-light-gray{background-color:#f7f7f7}.link-hover:hover{background-color:#1f679e}.white{color:#fff}.bg-white{background-color:#fff}.bg-blue{background-color:#408bc9}.pb2{padding-bottom:.5rem}.pb6{padding-bottom:8rem}.pt3{padding-top:1rem}.pt5{padding-top:4rem}.pv2{padding-top:.5rem;padding-bottom:.5rem}.ph3{padding-left:1rem;padding-right:1rem}.ph4{padding-left:2rem;padding-right:2rem}.ml0{margin-left:0}.mb1{margin-bottom:.25rem}.mb2{margin-bottom:.5rem}.mb3{margin-bottom:1rem}.mt5{margin-top:4rem}.ttu{text-transform:uppercase}.f4{font-size:1.25rem}.f5{font-size:1rem}.f6{font-size:.875rem}.f7{font-size:.75rem}.measure{max-width:30em}.center{margin-left:auto}.center{margin-right:auto}@media screen and (min-width:30em){.f2-ns{font-size:2.25rem}}@media screen and (min-width:30em) and (max-width:60em){.f5-m{font-size:1rem}}@media screen and (min-width:60em){.f4-l{font-size:1.25rem}}
@@ -66,7 +66,7 @@ const indexTemplate = `
</svg>
<h1 class="f4 f2-ns mt5 fw5">Congrats! You created a tunnel!</h1>
<p class="f6 f5-m f4-l measure lh-copy fw3">
Argo Tunnel exposes locally running applications to the internet by
Cloudflare Tunnel exposes locally running applications to the internet by
running an encrypted, virtual tunnel from your laptop or server to
Cloudflare's edge network.
</p>
@@ -74,7 +74,7 @@ const indexTemplate = `
<a
class="fw6 link white bg-blue ph4 pv2 br1 dib f5 link-hover"
style="border-bottom: 1px solid #1f679e"
href="https://developers.cloudflare.com/argo-tunnel/">
href="https://developers.cloudflare.com/cloudflare-one/connections/connect-apps">
Get started here
</a>
<section>
+1 -1
View File
@@ -1,6 +1,6 @@
---
basename: "cloudflared"
comment: "Cloudflare Argo Tunnel"
comment: "Cloudflare Tunnel"
copyright: "Cloudflare, Inc"
arch: "x86"
abi: "64"
+7 -7
View File
@@ -351,18 +351,17 @@ func serveTunnel(
)
switch protocol {
case connection.QUIC:
case connection.QUIC, connection.QUICWarp:
connOptions := config.ConnectionOptions(addr.UDP.String(), uint8(backoff.Retries()))
return ServeQUIC(ctx,
addr.UDP,
config,
connOptions,
controlStream,
connectedFuse,
reconnectCh,
gracefulShutdownC)
case connection.HTTP2:
case connection.HTTP2, connection.HTTP2Warp:
edgeConn, err := edgediscovery.DialEdge(ctx, dialTimeout, config.EdgeTLSConfigs[protocol], addr.TCP)
if err != nil {
connLog.Err(err).Msg("Unable to establish connection with Cloudflare edge")
@@ -509,15 +508,16 @@ func ServeQUIC(
config *TunnelConfig,
connOptions *tunnelpogs.ConnectionOptions,
controlStreamHandler connection.ControlStreamHandler,
connectedFuse connection.ConnectedFuse,
reconnectCh chan ReconnectSignal,
gracefulShutdownC <-chan struct{},
) (err error, recoverable bool) {
tlsConfig := config.EdgeTLSConfigs[connection.QUIC]
quicConfig := &quic.Config{
HandshakeIdleTimeout: quicHandshakeIdleTimeout,
MaxIdleTimeout: quicMaxIdleTimeout,
KeepAlive: true,
HandshakeIdleTimeout: quicHandshakeIdleTimeout,
MaxIdleTimeout: quicMaxIdleTimeout,
MaxIncomingStreams: connection.MaxConcurrentStreams,
MaxIncomingUniStreams: connection.MaxConcurrentStreams,
KeepAlive: true,
}
for {
select {
+6 -8
View File
@@ -8,20 +8,18 @@ import (
"github.com/stretchr/testify/assert"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/edgediscovery"
"github.com/cloudflare/cloudflared/retry"
)
type dynamicMockFetcher struct {
percentage int32
err error
protocolPercents edgediscovery.ProtocolPercents
err error
}
func (dmf *dynamicMockFetcher) fetch() connection.PercentageFetcher {
return func() (int32, error) {
if dmf.err != nil {
return 0, dmf.err
}
return dmf.percentage, nil
return func() (edgediscovery.ProtocolPercents, error) {
return dmf.protocolPercents, dmf.err
}
}
@@ -39,7 +37,7 @@ func TestWaitForBackoffFallback(t *testing.T) {
},
}
mockFetcher := dynamicMockFetcher{
percentage: 0,
protocolPercents: edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}},
}
warpRoutingEnabled := false
protocolSelector, err := connection.NewProtocolSelector(
+1 -1
View File
@@ -148,7 +148,7 @@ func validateScheme(scheme string) error {
return nil
}
}
return fmt.Errorf("Currently Argo Tunnel does not support %s protocol.", scheme)
return fmt.Errorf("Currently Cloudflare Tunnel does not support %s protocol.", scheme)
}
func validateIP(scheme, host, port string) (string, error) {
+1 -1
View File
@@ -109,7 +109,7 @@ func TestValidateUrl(t *testing.T) {
assert.Empty(t, validUrl)
validUrl, err = ValidateUrl("ftp://alex:12345@hello.example.com:8080/robot.txt")
assert.Equal(t, "Currently Argo Tunnel does not support ftp protocol.", err.Error())
assert.Equal(t, "Currently Cloudflare Tunnel does not support ftp protocol.", err.Error())
assert.Empty(t, validUrl)
}