mirror of
https://github.com/cloudflare/cloudflared
synced 2026-06-08 13:33:07 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d37ad42426 | |||
| 44e6d1a313 | |||
| 30197e7dfa | |||
| 654a326098 | |||
| 43446bc692 | |||
| e9f010111d | |||
| 8184bc457d | |||
| 2db00211f5 | |||
| eb2e4349e8 | |||
| 7d76ce2d24 | |||
| 66efd3f2bb |
@@ -4,7 +4,7 @@ jobs:
|
||||
check:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.21.x]
|
||||
go-version: [1.22.x]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
||||
Vendored
+2
-2
@@ -3,6 +3,6 @@
|
||||
cd /tmp
|
||||
git clone -q https://github.com/cloudflare/go
|
||||
cd go/src
|
||||
# https://github.com/cloudflare/go/tree/34129e47042e214121b6bbff0ded4712debed18e is version go1.21.5-devel-cf
|
||||
git checkout -q 34129e47042e214121b6bbff0ded4712debed18e
|
||||
# https://github.com/cloudflare/go/tree/ec0a014545f180b0c74dfd687698657a9e86e310 is version go1.22.2-devel-cf
|
||||
git checkout -q ec0a014545f180b0c74dfd687698657a9e86e310
|
||||
./make.bash
|
||||
+2
-2
@@ -9,8 +9,8 @@ Set-Location "$Env:Temp"
|
||||
git clone -q https://github.com/cloudflare/go
|
||||
Write-Output "Building go..."
|
||||
cd go/src
|
||||
# https://github.com/cloudflare/go/tree/34129e47042e214121b6bbff0ded4712debed18e is version go1.21.5-devel-cf
|
||||
git checkout -q 34129e47042e214121b6bbff0ded4712debed18e
|
||||
# https://github.com/cloudflare/go/tree/ec0a014545f180b0c74dfd687698657a9e86e310 is version go1.22.2-devel-cf
|
||||
git checkout -q ec0a014545f180b0c74dfd687698657a9e86e310
|
||||
& ./make.bat
|
||||
|
||||
Write-Output "Installed"
|
||||
Vendored
+7
-7
@@ -1,20 +1,20 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
$GoMsiVersion = "go1.21.5.windows-amd64.msi"
|
||||
|
||||
$GoMsiVersion = "go1.22.2.windows-amd64.msi"
|
||||
|
||||
Write-Output "Downloading go installer..."
|
||||
|
||||
|
||||
Set-Location "$Env:Temp"
|
||||
|
||||
|
||||
(New-Object System.Net.WebClient).DownloadFile(
|
||||
"https://go.dev/dl/$GoMsiVersion",
|
||||
"$Env:Temp\$GoMsiVersion"
|
||||
)
|
||||
|
||||
|
||||
Write-Output "Installing go..."
|
||||
Install-Package "$Env:Temp\$GoMsiVersion" -Force
|
||||
|
||||
|
||||
# Go installer updates global $PATH
|
||||
go env
|
||||
|
||||
|
||||
Write-Output "Installed"
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
# use a builder image for building cloudflare
|
||||
ARG TARGET_GOOS
|
||||
ARG TARGET_GOARCH
|
||||
FROM golang:1.21.5 as builder
|
||||
FROM golang:1.22.2 as builder
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0 \
|
||||
TARGET_GOOS=${TARGET_GOOS} \
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
# use a builder image for building cloudflare
|
||||
FROM golang:1.21.5 as builder
|
||||
FROM golang:1.22.2 as builder
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0
|
||||
|
||||
CGO_ENABLED=0
|
||||
|
||||
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
|
||||
# copy our sources into the builder image
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
# use a builder image for building cloudflare
|
||||
FROM golang:1.21.5 as builder
|
||||
FROM golang:1.22.2 as builder
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0
|
||||
|
||||
CGO_ENABLED=0
|
||||
|
||||
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
|
||||
# copy our sources into the builder image
|
||||
|
||||
@@ -251,17 +251,11 @@ github-windows-upload:
|
||||
python3 github_release.py --path built_artifacts/cloudflared-windows-386.exe --release-version $(VERSION) --name cloudflared-windows-386.exe
|
||||
python3 github_release.py --path built_artifacts/cloudflared-windows-386.msi --release-version $(VERSION) --name cloudflared-windows-386.msi
|
||||
|
||||
.PHONY: tunnelrpc-deps
|
||||
tunnelrpc-deps:
|
||||
.PHONY: capnp
|
||||
capnp:
|
||||
which capnp # https://capnproto.org/install.html
|
||||
which capnpc-go # go install zombiezen.com/go/capnproto2/capnpc-go@latest
|
||||
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
|
||||
capnp compile -ogo tunnelrpc/proto/tunnelrpc.capnp tunnelrpc/proto/quic_metadata_protocol.capnp
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
@@ -269,4 +263,4 @@ vet:
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
goimports -l -w -local github.com/cloudflare/cloudflared $$(go list -mod=vendor -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc)
|
||||
goimports -l -w -local github.com/cloudflare/cloudflared $$(go list -mod=vendor -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc/proto)
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
2024.6.0
|
||||
- 2024-05-30 TUN-8441: Correct UDP total sessions metric to a counter and add new ICMP metrics
|
||||
- 2024-05-28 TUN-8422: Add metrics for capnp method calls
|
||||
- 2024-05-24 TUN-8424: Refactor capnp registration server
|
||||
- 2024-05-23 TUN-8427: Fix BackoffHandler's internally shared clock structure
|
||||
- 2024-05-21 TUN-8425: Remove ICMP binding for quick tunnels
|
||||
- 2024-05-20 TUN-8423: Deprecate older legacy tunnel capnp interfaces
|
||||
- 2024-05-15 TUN-8419: Add capnp safe transport
|
||||
- 2024-05-13 TUN-8415: Refactor capnp rpc into a single module
|
||||
|
||||
2024.5.0
|
||||
- 2024-05-07 TUN-8407: Upgrade go to version 1.22.2
|
||||
|
||||
2024.4.1
|
||||
- 2024-04-22 TUN-8380: Add sleep before requesting quick tunnel as temporary fix for component tests
|
||||
- 2024-04-19 TUN-8374: Close UDP socket if registration fails
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
pinned_go: &pinned_go go-boring=1.21.5-1
|
||||
pinned_go: &pinned_go go-boring=1.22.2-1
|
||||
|
||||
build_dir: &build_dir /cfsetup_build
|
||||
default-flavor: bullseye
|
||||
@@ -33,11 +33,11 @@ buster: &buster
|
||||
- ./build-packages-fips.sh
|
||||
# Build binary for component test
|
||||
- GOOS=linux GOARCH=amd64 make cloudflared
|
||||
cover:
|
||||
cover:
|
||||
build_dir: *build_dir
|
||||
builddeps: *build_deps
|
||||
pre-cache: *build_pre_cache
|
||||
post-cache:
|
||||
post-cache:
|
||||
- make cover
|
||||
# except FIPS (handled in github-fips-release-pkgs) and macos (handled in github-release-macos-amd64)
|
||||
github-release-pkgs:
|
||||
@@ -96,7 +96,7 @@ buster: &buster
|
||||
- make github-release-built-pkgs
|
||||
generate-versions-file:
|
||||
build_dir: *build_dir
|
||||
builddeps:
|
||||
builddeps:
|
||||
- *pinned_go
|
||||
- build-essential
|
||||
post-cache:
|
||||
|
||||
@@ -78,8 +78,8 @@ const (
|
||||
// hostKeyPath is the path of the dir to save SSH host keys too
|
||||
hostKeyPath = "host-key-path"
|
||||
|
||||
// udpUnregisterSessionTimeout is how long we wait before we stop trying to unregister a UDP session from the edge
|
||||
udpUnregisterSessionTimeoutFlag = "udp-unregister-session-timeout"
|
||||
// rpcTimeout is how long to wait for a Capnp RPC request to the edge
|
||||
rpcTimeout = "rpc-timeout"
|
||||
|
||||
// writeStreamTimeout sets if we should have a timeout when writing data to a stream towards the destination (edge/origin).
|
||||
writeStreamTimeout = "write-stream-timeout"
|
||||
@@ -287,7 +287,7 @@ func routeFromFlag(c *cli.Context) (route cfapi.HostnameRoute, ok bool) {
|
||||
func StartServer(
|
||||
c *cli.Context,
|
||||
info *cliutil.BuildInfo,
|
||||
namedTunnel *connection.NamedTunnelProperties,
|
||||
namedTunnel *connection.TunnelProperties,
|
||||
log *zerolog.Logger,
|
||||
) error {
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
@@ -409,6 +409,11 @@ func StartServer(
|
||||
}
|
||||
}
|
||||
|
||||
// Disable ICMP packet routing for quick tunnels
|
||||
if quickTunnelURL != "" {
|
||||
tunnelConfig.PacketConfig = nil
|
||||
}
|
||||
|
||||
internalRules := []ingress.Rule{}
|
||||
if features.Contains(features.FeatureManagementLogs) {
|
||||
serviceIP := c.String("service-op-ip")
|
||||
@@ -658,9 +663,9 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||
}),
|
||||
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
|
||||
Name: "tag",
|
||||
Usage: "Custom tags used to identify this tunnel, in format `KEY=VALUE`. Multiple tags may be specified",
|
||||
Usage: "Custom tags used to identify this tunnel via added HTTP request headers to the origin, in format `KEY=VALUE`. Multiple tags may be specified.",
|
||||
EnvVars: []string{"TUNNEL_TAG"},
|
||||
Hidden: shouldHide,
|
||||
Hidden: true,
|
||||
}),
|
||||
altsrc.NewDurationFlag(&cli.DurationFlag{
|
||||
Name: "heartbeat-interval",
|
||||
@@ -695,7 +700,7 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||
Hidden: true,
|
||||
}),
|
||||
altsrc.NewDurationFlag(&cli.DurationFlag{
|
||||
Name: udpUnregisterSessionTimeoutFlag,
|
||||
Name: rpcTimeout,
|
||||
Value: 5 * time.Second,
|
||||
Hidden: true,
|
||||
}),
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/cloudflare/cloudflared/orchestration"
|
||||
"github.com/cloudflare/cloudflared/supervisor"
|
||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -108,7 +108,7 @@ func isSecretEnvVar(key string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func dnsProxyStandAlone(c *cli.Context, namedTunnel *connection.NamedTunnelProperties) bool {
|
||||
func dnsProxyStandAlone(c *cli.Context, namedTunnel *connection.TunnelProperties) bool {
|
||||
return c.IsSet("proxy-dns") &&
|
||||
!(c.IsSet("name") || // adhoc-named tunnel
|
||||
c.IsSet(ingress.HelloWorldFlag) || // quick or named tunnel
|
||||
@@ -121,7 +121,7 @@ func prepareTunnelConfig(
|
||||
info *cliutil.BuildInfo,
|
||||
log, logTransport *zerolog.Logger,
|
||||
observer *connection.Observer,
|
||||
namedTunnel *connection.NamedTunnelProperties,
|
||||
namedTunnel *connection.TunnelProperties,
|
||||
) (*supervisor.TunnelConfig, *orchestration.Config, error) {
|
||||
clientID, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
@@ -133,7 +133,7 @@ func prepareTunnelConfig(
|
||||
log.Err(err).Msg("Tag parse failure")
|
||||
return nil, nil, errors.Wrap(err, "Tag parse failure")
|
||||
}
|
||||
tags = append(tags, tunnelpogs.Tag{Name: "ID", Value: clientID.String()})
|
||||
tags = append(tags, pogs.Tag{Name: "ID", Value: clientID.String()})
|
||||
|
||||
transportProtocol := c.String("protocol")
|
||||
|
||||
@@ -166,7 +166,7 @@ func prepareTunnelConfig(
|
||||
)
|
||||
}
|
||||
|
||||
namedTunnel.Client = tunnelpogs.ClientInfo{
|
||||
namedTunnel.Client = pogs.ClientInfo{
|
||||
ClientID: clientID[:],
|
||||
Features: clientFeatures,
|
||||
Version: info.Version(),
|
||||
@@ -246,7 +246,7 @@ func prepareTunnelConfig(
|
||||
EdgeTLSConfigs: edgeTLSConfigs,
|
||||
FeatureSelector: featureSelector,
|
||||
MaxEdgeAddrRetries: uint8(c.Int("max-edge-addr-retries")),
|
||||
UDPUnregisterSessionTimeout: c.Duration(udpUnregisterSessionTimeoutFlag),
|
||||
RPCTimeout: c.Duration(rpcTimeout),
|
||||
WriteStreamTimeout: c.Duration(writeStreamTimeout),
|
||||
DisableQUICPathMTUDiscovery: c.Bool(quicDisablePathMTUDiscovery),
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func RunQuickTunnel(sc *subcommandContext) error {
|
||||
return StartServer(
|
||||
sc.c,
|
||||
buildInfo,
|
||||
&connection.NamedTunnelProperties{Credentials: credentials, QuickTunnelUrl: data.Result.Hostname},
|
||||
&connection.TunnelProperties{Credentials: credentials, QuickTunnelUrl: data.Result.Hostname},
|
||||
sc.log,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ func (sc *subcommandContext) runWithCredentials(credentials connection.Credentia
|
||||
return StartServer(
|
||||
sc.c,
|
||||
buildInfo,
|
||||
&connection.NamedTunnelProperties{Credentials: credentials},
|
||||
&connection.TunnelProperties{Credentials: credentials},
|
||||
sc.log,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,23 +4,23 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// Restrict key names to characters allowed in an HTTP header name.
|
||||
// Restrict key values to printable characters (what is recognised as data in an HTTP header value).
|
||||
var tagRegexp = regexp.MustCompile("^([a-zA-Z0-9!#$%&'*+\\-.^_`|~]+)=([[:print:]]+)$")
|
||||
|
||||
func NewTagFromCLI(compoundTag string) (tunnelpogs.Tag, bool) {
|
||||
func NewTagFromCLI(compoundTag string) (pogs.Tag, bool) {
|
||||
matches := tagRegexp.FindStringSubmatch(compoundTag)
|
||||
if len(matches) == 0 {
|
||||
return tunnelpogs.Tag{}, false
|
||||
return pogs.Tag{}, false
|
||||
}
|
||||
return tunnelpogs.Tag{Name: matches[1], Value: matches[2]}, true
|
||||
return pogs.Tag{Name: matches[1], Value: matches[2]}, true
|
||||
}
|
||||
|
||||
func NewTagSliceFromCLI(tags []string) ([]tunnelpogs.Tag, error) {
|
||||
var tagSlice []tunnelpogs.Tag
|
||||
func NewTagSliceFromCLI(tags []string) ([]pogs.Tag, error) {
|
||||
var tagSlice []pogs.Tag
|
||||
for _, compoundTag := range tags {
|
||||
if tag, ok := NewTagFromCLI(compoundTag); ok {
|
||||
tagSlice = append(tagSlice, tag)
|
||||
|
||||
@@ -3,7 +3,7 @@ package tunnel
|
||||
import (
|
||||
"testing"
|
||||
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -11,12 +11,12 @@ import (
|
||||
func TestSingleTag(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output tunnelpogs.Tag
|
||||
Output pogs.Tag
|
||||
Fail bool
|
||||
}{
|
||||
{Input: "x=y", Output: tunnelpogs.Tag{Name: "x", Value: "y"}},
|
||||
{Input: "More-Complex=Tag Values", Output: tunnelpogs.Tag{Name: "More-Complex", Value: "Tag Values"}},
|
||||
{Input: "First=Equals=Wins", Output: tunnelpogs.Tag{Name: "First", Value: "Equals=Wins"}},
|
||||
{Input: "x=y", Output: pogs.Tag{Name: "x", Value: "y"}},
|
||||
{Input: "More-Complex=Tag Values", Output: pogs.Tag{Name: "More-Complex", Value: "Tag Values"}},
|
||||
{Input: "First=Equals=Wins", Output: pogs.Tag{Name: "First", Value: "Equals=Wins"}},
|
||||
{Input: "x=", Fail: true},
|
||||
{Input: "=y", Fail: true},
|
||||
{Input: "=", Fail: true},
|
||||
|
||||
@@ -42,7 +42,7 @@ type Orchestrator interface {
|
||||
GetOriginProxy() (OriginProxy, error)
|
||||
}
|
||||
|
||||
type NamedTunnelProperties struct {
|
||||
type TunnelProperties struct {
|
||||
Credentials Credentials
|
||||
Client pogs.ClientInfo
|
||||
QuickTunnelUrl string
|
||||
|
||||
+48
-32
@@ -6,25 +6,25 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/management"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// RPCClientFunc derives a named tunnel rpc client that can then be used to register and unregister connections.
|
||||
type RPCClientFunc func(context.Context, io.ReadWriteCloser, *zerolog.Logger) NamedTunnelRPCClient
|
||||
// registerClient derives a named tunnel rpc client that can then be used to register and unregister connections.
|
||||
type registerClientFunc func(context.Context, io.ReadWriteCloser, time.Duration) tunnelrpc.RegistrationClient
|
||||
|
||||
type controlStream struct {
|
||||
observer *Observer
|
||||
|
||||
connectedFuse ConnectedFuse
|
||||
namedTunnelProperties *NamedTunnelProperties
|
||||
connIndex uint8
|
||||
edgeAddress net.IP
|
||||
protocol Protocol
|
||||
connectedFuse ConnectedFuse
|
||||
tunnelProperties *TunnelProperties
|
||||
connIndex uint8
|
||||
edgeAddress net.IP
|
||||
protocol Protocol
|
||||
|
||||
newRPCClientFunc RPCClientFunc
|
||||
registerClientFunc registerClientFunc
|
||||
registerTimeout time.Duration
|
||||
|
||||
gracefulShutdownC <-chan struct{}
|
||||
gracePeriod time.Duration
|
||||
@@ -47,27 +47,29 @@ type TunnelConfigJSONGetter interface {
|
||||
func NewControlStream(
|
||||
observer *Observer,
|
||||
connectedFuse ConnectedFuse,
|
||||
namedTunnelConfig *NamedTunnelProperties,
|
||||
tunnelProperties *TunnelProperties,
|
||||
connIndex uint8,
|
||||
edgeAddress net.IP,
|
||||
newRPCClientFunc RPCClientFunc,
|
||||
registerClientFunc registerClientFunc,
|
||||
registerTimeout time.Duration,
|
||||
gracefulShutdownC <-chan struct{},
|
||||
gracePeriod time.Duration,
|
||||
protocol Protocol,
|
||||
) ControlStreamHandler {
|
||||
if newRPCClientFunc == nil {
|
||||
newRPCClientFunc = newRegistrationRPCClient
|
||||
if registerClientFunc == nil {
|
||||
registerClientFunc = tunnelrpc.NewRegistrationClient
|
||||
}
|
||||
return &controlStream{
|
||||
observer: observer,
|
||||
connectedFuse: connectedFuse,
|
||||
namedTunnelProperties: namedTunnelConfig,
|
||||
newRPCClientFunc: newRPCClientFunc,
|
||||
connIndex: connIndex,
|
||||
edgeAddress: edgeAddress,
|
||||
gracefulShutdownC: gracefulShutdownC,
|
||||
gracePeriod: gracePeriod,
|
||||
protocol: protocol,
|
||||
observer: observer,
|
||||
connectedFuse: connectedFuse,
|
||||
tunnelProperties: tunnelProperties,
|
||||
registerClientFunc: registerClientFunc,
|
||||
registerTimeout: registerTimeout,
|
||||
connIndex: connIndex,
|
||||
edgeAddress: edgeAddress,
|
||||
gracefulShutdownC: gracefulShutdownC,
|
||||
gracePeriod: gracePeriod,
|
||||
protocol: protocol,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,13 +79,25 @@ func (c *controlStream) ServeControlStream(
|
||||
connOptions *tunnelpogs.ConnectionOptions,
|
||||
tunnelConfigGetter TunnelConfigJSONGetter,
|
||||
) error {
|
||||
rpcClient := c.newRPCClientFunc(ctx, rw, c.observer.log)
|
||||
registrationClient := c.registerClientFunc(ctx, rw, c.registerTimeout)
|
||||
|
||||
registrationDetails, err := rpcClient.RegisterConnection(ctx, c.namedTunnelProperties, connOptions, c.connIndex, c.edgeAddress, c.observer)
|
||||
registrationDetails, err := registrationClient.RegisterConnection(
|
||||
ctx,
|
||||
c.tunnelProperties.Credentials.Auth(),
|
||||
c.tunnelProperties.Credentials.TunnelID,
|
||||
connOptions,
|
||||
c.connIndex,
|
||||
c.edgeAddress)
|
||||
if err != nil {
|
||||
rpcClient.Close()
|
||||
return err
|
||||
defer registrationClient.Close()
|
||||
if err.Error() == DuplicateConnectionError {
|
||||
c.observer.metrics.regFail.WithLabelValues("dup_edge_conn", "registerConnection").Inc()
|
||||
return errDuplicationConnection
|
||||
}
|
||||
c.observer.metrics.regFail.WithLabelValues("server_error", "registerConnection").Inc()
|
||||
return serverRegistrationErrorFromRPC(err)
|
||||
}
|
||||
c.observer.metrics.regSuccess.WithLabelValues("registerConnection").Inc()
|
||||
|
||||
c.observer.logConnected(registrationDetails.UUID, c.connIndex, registrationDetails.Location, c.edgeAddress, c.protocol)
|
||||
c.observer.sendConnectedEvent(c.connIndex, c.protocol, registrationDetails.Location)
|
||||
@@ -92,21 +106,23 @@ func (c *controlStream) ServeControlStream(
|
||||
// if conn index is 0 and tunnel is not remotely managed, then send local ingress rules configuration
|
||||
if c.connIndex == 0 && !registrationDetails.TunnelIsRemotelyManaged {
|
||||
if tunnelConfig, err := tunnelConfigGetter.GetConfigJSON(); err == nil {
|
||||
if err := rpcClient.SendLocalConfiguration(ctx, tunnelConfig, c.observer); err != nil {
|
||||
if err := registrationClient.SendLocalConfiguration(ctx, tunnelConfig); err != nil {
|
||||
c.observer.metrics.localConfigMetrics.pushesErrors.Inc()
|
||||
c.observer.log.Err(err).Msg("unable to send local configuration")
|
||||
}
|
||||
c.observer.metrics.localConfigMetrics.pushes.Inc()
|
||||
} else {
|
||||
c.observer.log.Err(err).Msg("failed to obtain current configuration")
|
||||
}
|
||||
}
|
||||
|
||||
c.waitForUnregister(ctx, rpcClient)
|
||||
c.waitForUnregister(ctx, registrationClient)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *controlStream) waitForUnregister(ctx context.Context, rpcClient NamedTunnelRPCClient) {
|
||||
func (c *controlStream) waitForUnregister(ctx context.Context, registrationClient tunnelrpc.RegistrationClient) {
|
||||
// wait for connection termination or start of graceful shutdown
|
||||
defer rpcClient.Close()
|
||||
defer registrationClient.Close()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
break
|
||||
@@ -115,7 +131,7 @@ func (c *controlStream) waitForUnregister(ctx context.Context, rpcClient NamedTu
|
||||
}
|
||||
|
||||
c.observer.sendUnregisteringEvent(c.connIndex)
|
||||
rpcClient.GracefulShutdown(ctx, c.gracePeriod)
|
||||
registrationClient.GracefulShutdown(ctx, c.gracePeriod)
|
||||
c.observer.log.Info().
|
||||
Int(management.EventTypeKey, int(management.Cloudflared)).
|
||||
Uint8(LogFieldConnIndex, c.connIndex).
|
||||
|
||||
@@ -40,8 +40,6 @@ type HTTP2Connection struct {
|
||||
connOptions *tunnelpogs.ConnectionOptions
|
||||
observer *Observer
|
||||
connIndex uint8
|
||||
// newRPCClientFunc allows us to mock RPCs during testing
|
||||
newRPCClientFunc func(context.Context, io.ReadWriteCloser, *zerolog.Logger) NamedTunnelRPCClient
|
||||
|
||||
log *zerolog.Logger
|
||||
activeRequestsWG sync.WaitGroup
|
||||
@@ -69,7 +67,6 @@ func NewHTTP2Connection(
|
||||
connOptions: connOptions,
|
||||
observer: observer,
|
||||
connIndex: connIndex,
|
||||
newRPCClientFunc: newRegistrationRPCClient,
|
||||
controlStreamHandler: controlStreamHandler,
|
||||
log: log,
|
||||
}
|
||||
|
||||
+18
-14
@@ -20,8 +20,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -36,10 +36,11 @@ func newTestHTTP2Connection() (*HTTP2Connection, net.Conn) {
|
||||
controlStream := NewControlStream(
|
||||
obs,
|
||||
mockConnectedFuse{},
|
||||
&NamedTunnelProperties{},
|
||||
&TunnelProperties{},
|
||||
connIndex,
|
||||
nil,
|
||||
nil,
|
||||
1*time.Second,
|
||||
nil,
|
||||
1*time.Second,
|
||||
HTTP2,
|
||||
@@ -168,23 +169,23 @@ type mockNamedTunnelRPCClient struct {
|
||||
unregistered chan struct{}
|
||||
}
|
||||
|
||||
func (mc mockNamedTunnelRPCClient) SendLocalConfiguration(c context.Context, config []byte, observer *Observer) error {
|
||||
func (mc mockNamedTunnelRPCClient) SendLocalConfiguration(c context.Context, config []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mc mockNamedTunnelRPCClient) RegisterConnection(
|
||||
c context.Context,
|
||||
properties *NamedTunnelProperties,
|
||||
options *tunnelpogs.ConnectionOptions,
|
||||
ctx context.Context,
|
||||
auth pogs.TunnelAuth,
|
||||
tunnelID uuid.UUID,
|
||||
options *pogs.ConnectionOptions,
|
||||
connIndex uint8,
|
||||
edgeAddress net.IP,
|
||||
observer *Observer,
|
||||
) (*tunnelpogs.ConnectionDetails, error) {
|
||||
) (*pogs.ConnectionDetails, error) {
|
||||
if mc.shouldFail != nil {
|
||||
return nil, mc.shouldFail
|
||||
}
|
||||
close(mc.registered)
|
||||
return &tunnelpogs.ConnectionDetails{
|
||||
return &pogs.ConnectionDetails{
|
||||
Location: "LIS",
|
||||
UUID: uuid.New(),
|
||||
TunnelIsRemotelyManaged: false,
|
||||
@@ -203,8 +204,8 @@ type mockRPCClientFactory struct {
|
||||
unregistered chan struct{}
|
||||
}
|
||||
|
||||
func (mf *mockRPCClientFactory) newMockRPCClient(context.Context, io.ReadWriteCloser, *zerolog.Logger) NamedTunnelRPCClient {
|
||||
return mockNamedTunnelRPCClient{
|
||||
func (mf *mockRPCClientFactory) newMockRPCClient(context.Context, io.ReadWriteCloser, time.Duration) tunnelrpc.RegistrationClient {
|
||||
return &mockNamedTunnelRPCClient{
|
||||
shouldFail: mf.shouldFail,
|
||||
registered: mf.registered,
|
||||
unregistered: mf.unregistered,
|
||||
@@ -360,10 +361,11 @@ func TestServeControlStream(t *testing.T) {
|
||||
controlStream := NewControlStream(
|
||||
obs,
|
||||
mockConnectedFuse{},
|
||||
&NamedTunnelProperties{},
|
||||
&TunnelProperties{},
|
||||
1,
|
||||
nil,
|
||||
rpcClientFactory.newMockRPCClient,
|
||||
1*time.Second,
|
||||
nil,
|
||||
1*time.Second,
|
||||
HTTP2,
|
||||
@@ -412,10 +414,11 @@ func TestFailRegistration(t *testing.T) {
|
||||
controlStream := NewControlStream(
|
||||
obs,
|
||||
mockConnectedFuse{},
|
||||
&NamedTunnelProperties{},
|
||||
&TunnelProperties{},
|
||||
http2Conn.connIndex,
|
||||
nil,
|
||||
rpcClientFactory.newMockRPCClient,
|
||||
1*time.Second,
|
||||
nil,
|
||||
1*time.Second,
|
||||
HTTP2,
|
||||
@@ -460,10 +463,11 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
|
||||
controlStream := NewControlStream(
|
||||
obs,
|
||||
mockConnectedFuse{},
|
||||
&NamedTunnelProperties{},
|
||||
&TunnelProperties{},
|
||||
http2Conn.connIndex,
|
||||
nil,
|
||||
rpcClientFactory.newMockRPCClient,
|
||||
1*time.Second,
|
||||
shutdownC,
|
||||
1*time.Second,
|
||||
HTTP2,
|
||||
|
||||
@@ -43,7 +43,6 @@ type localConfigMetrics struct {
|
||||
}
|
||||
|
||||
type tunnelMetrics struct {
|
||||
timerRetries prometheus.Gauge
|
||||
serverLocations *prometheus.GaugeVec
|
||||
// locationLock is a mutex for oldServerLocations
|
||||
locationLock sync.Mutex
|
||||
@@ -351,15 +350,6 @@ func initTunnelMetrics() *tunnelMetrics {
|
||||
)
|
||||
prometheus.MustRegister(maxConcurrentRequestsPerTunnel)
|
||||
|
||||
timerRetries := prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: MetricsNamespace,
|
||||
Subsystem: TunnelSubsystem,
|
||||
Name: "timer_retries",
|
||||
Help: "Unacknowledged heart beats count",
|
||||
})
|
||||
prometheus.MustRegister(timerRetries)
|
||||
|
||||
serverLocations := prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: MetricsNamespace,
|
||||
@@ -416,7 +406,6 @@ func initTunnelMetrics() *tunnelMetrics {
|
||||
prometheus.MustRegister(registerSuccess)
|
||||
|
||||
return &tunnelMetrics{
|
||||
timerRetries: timerRetries,
|
||||
serverLocations: serverLocations,
|
||||
oldServerLocations: make(map[string]string),
|
||||
muxerMetrics: newMuxerMetrics(),
|
||||
|
||||
+34
-62
@@ -28,9 +28,11 @@ import (
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
"github.com/cloudflare/cloudflared/management"
|
||||
"github.com/cloudflare/cloudflared/packet"
|
||||
quicpogs "github.com/cloudflare/cloudflared/quic"
|
||||
cfdquic "github.com/cloudflare/cloudflared/quic"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
rpcquic "github.com/cloudflare/cloudflared/tunnelrpc/quic"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -59,14 +61,14 @@ type QUICConnection struct {
|
||||
// sessionManager tracks active sessions. It receives datagrams from quic connection via datagramMuxer
|
||||
sessionManager datagramsession.Manager
|
||||
// datagramMuxer mux/demux datagrams from quic connection
|
||||
datagramMuxer *quicpogs.DatagramMuxerV2
|
||||
datagramMuxer *cfdquic.DatagramMuxerV2
|
||||
packetRouter *ingress.PacketRouter
|
||||
controlStreamHandler ControlStreamHandler
|
||||
connOptions *tunnelpogs.ConnectionOptions
|
||||
connIndex uint8
|
||||
|
||||
udpUnregisterTimeout time.Duration
|
||||
streamWriteTimeout time.Duration
|
||||
rpcTimeout time.Duration
|
||||
streamWriteTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewQUICConnection returns a new instance of QUICConnection.
|
||||
@@ -82,7 +84,7 @@ func NewQUICConnection(
|
||||
controlStreamHandler ControlStreamHandler,
|
||||
logger *zerolog.Logger,
|
||||
packetRouterConfig *ingress.GlobalRouterConfig,
|
||||
udpUnregisterTimeout time.Duration,
|
||||
rpcTimeout time.Duration,
|
||||
streamWriteTimeout time.Duration,
|
||||
) (*QUICConnection, error) {
|
||||
udpConn, err := createUDPConnForConnIndex(connIndex, localAddr, logger)
|
||||
@@ -104,7 +106,7 @@ func NewQUICConnection(
|
||||
}
|
||||
|
||||
sessionDemuxChan := make(chan *packet.Session, demuxChanCapacity)
|
||||
datagramMuxer := quicpogs.NewDatagramMuxerV2(session, logger, sessionDemuxChan)
|
||||
datagramMuxer := cfdquic.NewDatagramMuxerV2(session, logger, sessionDemuxChan)
|
||||
sessionManager := datagramsession.NewManager(logger, datagramMuxer.SendToSession, sessionDemuxChan)
|
||||
packetRouter := ingress.NewPacketRouter(packetRouterConfig, datagramMuxer, logger)
|
||||
|
||||
@@ -118,7 +120,7 @@ func NewQUICConnection(
|
||||
controlStreamHandler: controlStreamHandler,
|
||||
connOptions: connOptions,
|
||||
connIndex: connIndex,
|
||||
udpUnregisterTimeout: udpUnregisterTimeout,
|
||||
rpcTimeout: rpcTimeout,
|
||||
streamWriteTimeout: streamWriteTimeout,
|
||||
}, nil
|
||||
}
|
||||
@@ -198,7 +200,7 @@ func (q *QUICConnection) acceptStream(ctx context.Context) error {
|
||||
|
||||
func (q *QUICConnection) runStream(quicStream quic.Stream) {
|
||||
ctx := quicStream.Context()
|
||||
stream := quicpogs.NewSafeStreamCloser(quicStream, q.streamWriteTimeout, q.logger)
|
||||
stream := cfdquic.NewSafeStreamCloser(quicStream, q.streamWriteTimeout, q.logger)
|
||||
defer stream.Close()
|
||||
|
||||
// we are going to fuse readers/writers from stream <- cloudflared -> origin, and we want to guarantee that
|
||||
@@ -206,7 +208,8 @@ func (q *QUICConnection) runStream(quicStream quic.Stream) {
|
||||
// So, we wrap the stream with a no-op write closer and only this method can actually close write side of the stream.
|
||||
// A call to close will simulate a close to the read-side, which will fail subsequent reads.
|
||||
noCloseStream := &nopCloserReadWriter{ReadWriteCloser: stream}
|
||||
if err := q.handleStream(ctx, noCloseStream); err != nil {
|
||||
ss := rpcquic.NewCloudflaredServer(q.handleDataStream, q, q, q.rpcTimeout)
|
||||
if err := ss.Serve(ctx, noCloseStream); err != nil {
|
||||
q.logger.Debug().Err(err).Msg("Failed to handle QUIC stream")
|
||||
|
||||
// if we received an error at this level, then close write side of stream with an error, which will result in
|
||||
@@ -215,30 +218,7 @@ func (q *QUICConnection) runStream(quicStream quic.Stream) {
|
||||
}
|
||||
}
|
||||
|
||||
func (q *QUICConnection) handleStream(ctx context.Context, stream io.ReadWriteCloser) error {
|
||||
signature, err := quicpogs.DetermineProtocol(stream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch signature {
|
||||
case quicpogs.DataStreamProtocolSignature:
|
||||
reqServerStream, err := quicpogs.NewRequestServerStream(stream, signature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return q.handleDataStream(ctx, reqServerStream)
|
||||
case quicpogs.RPCStreamProtocolSignature:
|
||||
rpcStream, err := quicpogs.NewRPCServerStream(stream, signature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return q.handleRPCStream(rpcStream)
|
||||
default:
|
||||
return fmt.Errorf("unknown protocol %v", signature)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *QUICConnection) handleDataStream(ctx context.Context, stream *quicpogs.RequestServerStream) error {
|
||||
func (q *QUICConnection) handleDataStream(ctx context.Context, stream *rpcquic.RequestServerStream) error {
|
||||
request, err := stream.ReadConnectRequestData()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -264,22 +244,22 @@ func (q *QUICConnection) handleDataStream(ctx context.Context, stream *quicpogs.
|
||||
// dispatchRequest will dispatch the request depending on the type and returns an error if it occurs.
|
||||
// More importantly, it also tells if the during processing of the request the ConnectResponse metadata was sent downstream.
|
||||
// This is important since it informs
|
||||
func (q *QUICConnection) dispatchRequest(ctx context.Context, stream *quicpogs.RequestServerStream, err error, request *quicpogs.ConnectRequest) (error, bool) {
|
||||
func (q *QUICConnection) dispatchRequest(ctx context.Context, stream *rpcquic.RequestServerStream, err error, request *pogs.ConnectRequest) (error, bool) {
|
||||
originProxy, err := q.orchestrator.GetOriginProxy()
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
|
||||
switch request.Type {
|
||||
case quicpogs.ConnectionTypeHTTP, quicpogs.ConnectionTypeWebsocket:
|
||||
case pogs.ConnectionTypeHTTP, pogs.ConnectionTypeWebsocket:
|
||||
tracedReq, err := buildHTTPRequest(ctx, request, stream, q.connIndex, q.logger)
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
w := newHTTPResponseAdapter(stream)
|
||||
return originProxy.ProxyHTTP(&w, tracedReq, request.Type == quicpogs.ConnectionTypeWebsocket), w.connectResponseSent
|
||||
return originProxy.ProxyHTTP(&w, tracedReq, request.Type == pogs.ConnectionTypeWebsocket), w.connectResponseSent
|
||||
|
||||
case quicpogs.ConnectionTypeTCP:
|
||||
case pogs.ConnectionTypeTCP:
|
||||
rwa := &streamReadWriteAcker{RequestServerStream: stream}
|
||||
metadata := request.MetadataMap()
|
||||
return originProxy.ProxyTCP(ctx, rwa, &TCPRequest{
|
||||
@@ -293,14 +273,6 @@ func (q *QUICConnection) dispatchRequest(ctx context.Context, stream *quicpogs.R
|
||||
}
|
||||
}
|
||||
|
||||
func (q *QUICConnection) handleRPCStream(rpcStream *quicpogs.RPCServerStream) error {
|
||||
if err := rpcStream.Serve(q, q, q.logger); err != nil {
|
||||
q.logger.Err(err).Msg("failed handling RPC stream")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterUdpSession is the RPC method invoked by edge to register and run a session
|
||||
func (q *QUICConnection) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration, traceContext string) (*tunnelpogs.RegisterUdpSessionResponse, error) {
|
||||
traceCtx := tracing.NewTracedContext(ctx, traceContext, q.logger)
|
||||
@@ -377,9 +349,9 @@ func (q *QUICConnection) closeUDPSession(ctx context.Context, sessionID uuid.UUI
|
||||
return
|
||||
}
|
||||
|
||||
stream := quicpogs.NewSafeStreamCloser(quicStream, q.streamWriteTimeout, q.logger)
|
||||
stream := cfdquic.NewSafeStreamCloser(quicStream, q.streamWriteTimeout, q.logger)
|
||||
defer stream.Close()
|
||||
rpcClientStream, err := quicpogs.NewRPCClientStream(ctx, stream, q.udpUnregisterTimeout, q.logger)
|
||||
rpcClientStream, err := rpcquic.NewSessionClient(ctx, stream, q.rpcTimeout)
|
||||
if err != nil {
|
||||
// Log this at debug because this is not an error if session was closed due to lost connection
|
||||
// with edge
|
||||
@@ -408,16 +380,16 @@ func (q *QUICConnection) UpdateConfiguration(ctx context.Context, version int32,
|
||||
// streamReadWriteAcker is a light wrapper over QUIC streams with a callback to send response back to
|
||||
// the client.
|
||||
type streamReadWriteAcker struct {
|
||||
*quicpogs.RequestServerStream
|
||||
*rpcquic.RequestServerStream
|
||||
connectResponseSent bool
|
||||
}
|
||||
|
||||
// AckConnection acks response back to the proxy.
|
||||
func (s *streamReadWriteAcker) AckConnection(tracePropagation string) error {
|
||||
metadata := []quicpogs.Metadata{}
|
||||
metadata := []pogs.Metadata{}
|
||||
// Only add tracing if provided by origintunneld
|
||||
if tracePropagation != "" {
|
||||
metadata = append(metadata, quicpogs.Metadata{
|
||||
metadata = append(metadata, pogs.Metadata{
|
||||
Key: tracing.CanonicalCloudflaredTracingHeader,
|
||||
Val: tracePropagation,
|
||||
})
|
||||
@@ -428,12 +400,12 @@ func (s *streamReadWriteAcker) AckConnection(tracePropagation string) error {
|
||||
|
||||
// httpResponseAdapter translates responses written by the HTTP Proxy into ones that can be used in QUIC.
|
||||
type httpResponseAdapter struct {
|
||||
*quicpogs.RequestServerStream
|
||||
*rpcquic.RequestServerStream
|
||||
headers http.Header
|
||||
connectResponseSent bool
|
||||
}
|
||||
|
||||
func newHTTPResponseAdapter(s *quicpogs.RequestServerStream) httpResponseAdapter {
|
||||
func newHTTPResponseAdapter(s *rpcquic.RequestServerStream) httpResponseAdapter {
|
||||
return httpResponseAdapter{RequestServerStream: s, headers: make(http.Header)}
|
||||
}
|
||||
|
||||
@@ -442,12 +414,12 @@ func (hrw *httpResponseAdapter) AddTrailer(trailerName, trailerValue string) {
|
||||
}
|
||||
|
||||
func (hrw *httpResponseAdapter) WriteRespHeaders(status int, header http.Header) error {
|
||||
metadata := make([]quicpogs.Metadata, 0)
|
||||
metadata = append(metadata, quicpogs.Metadata{Key: "HttpStatus", Val: strconv.Itoa(status)})
|
||||
metadata := make([]pogs.Metadata, 0)
|
||||
metadata = append(metadata, pogs.Metadata{Key: "HttpStatus", Val: strconv.Itoa(status)})
|
||||
for k, vv := range header {
|
||||
for _, v := range vv {
|
||||
httpHeaderKey := fmt.Sprintf("%s:%s", HTTPHeaderKey, k)
|
||||
metadata = append(metadata, quicpogs.Metadata{Key: httpHeaderKey, Val: v})
|
||||
metadata = append(metadata, pogs.Metadata{Key: httpHeaderKey, Val: v})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,17 +455,17 @@ func (hrw *httpResponseAdapter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
}
|
||||
|
||||
func (hrw *httpResponseAdapter) WriteErrorResponse(err error) {
|
||||
hrw.WriteConnectResponseData(err, quicpogs.Metadata{Key: "HttpStatus", Val: strconv.Itoa(http.StatusBadGateway)})
|
||||
hrw.WriteConnectResponseData(err, pogs.Metadata{Key: "HttpStatus", Val: strconv.Itoa(http.StatusBadGateway)})
|
||||
}
|
||||
|
||||
func (hrw *httpResponseAdapter) WriteConnectResponseData(respErr error, metadata ...quicpogs.Metadata) error {
|
||||
func (hrw *httpResponseAdapter) WriteConnectResponseData(respErr error, metadata ...pogs.Metadata) error {
|
||||
hrw.connectResponseSent = true
|
||||
return hrw.RequestServerStream.WriteConnectResponseData(respErr, metadata...)
|
||||
}
|
||||
|
||||
func buildHTTPRequest(
|
||||
ctx context.Context,
|
||||
connectRequest *quicpogs.ConnectRequest,
|
||||
connectRequest *pogs.ConnectRequest,
|
||||
body io.ReadCloser,
|
||||
connIndex uint8,
|
||||
log *zerolog.Logger,
|
||||
@@ -502,7 +474,7 @@ func buildHTTPRequest(
|
||||
dest := connectRequest.Dest
|
||||
method := metadata[HTTPMethodKey]
|
||||
host := metadata[HTTPHostKey]
|
||||
isWebsocket := connectRequest.Type == quicpogs.ConnectionTypeWebsocket
|
||||
isWebsocket := connectRequest.Type == pogs.ConnectionTypeWebsocket
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, dest, body)
|
||||
if err != nil {
|
||||
@@ -597,11 +569,11 @@ func (np *nopCloserReadWriter) Close() error {
|
||||
|
||||
// muxerWrapper wraps DatagramMuxerV2 to satisfy the packet.FunnelUniPipe interface
|
||||
type muxerWrapper struct {
|
||||
muxer *quicpogs.DatagramMuxerV2
|
||||
muxer *cfdquic.DatagramMuxerV2
|
||||
}
|
||||
|
||||
func (rp *muxerWrapper) SendPacket(dst netip.Addr, pk packet.RawPacket) error {
|
||||
return rp.muxer.SendPacket(quicpogs.RawPacket(pk))
|
||||
return rp.muxer.SendPacket(cfdquic.RawPacket(pk))
|
||||
}
|
||||
|
||||
func (rp *muxerWrapper) ReceivePacket(ctx context.Context) (packet.RawPacket, error) {
|
||||
@@ -609,7 +581,7 @@ func (rp *muxerWrapper) ReceivePacket(ctx context.Context) (packet.RawPacket, er
|
||||
if err != nil {
|
||||
return packet.RawPacket{}, err
|
||||
}
|
||||
rawPacket, ok := pk.(quicpogs.RawPacket)
|
||||
rawPacket, ok := pk.(cfdquic.RawPacket)
|
||||
if ok {
|
||||
return packet.RawPacket(rawPacket), nil
|
||||
}
|
||||
|
||||
+72
-40
@@ -3,9 +3,14 @@ package connection
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -23,14 +28,15 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cloudflare/cloudflared/datagramsession"
|
||||
quicpogs "github.com/cloudflare/cloudflared/quic"
|
||||
cfdquic "github.com/cloudflare/cloudflared/quic"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
rpcquic "github.com/cloudflare/cloudflared/tunnelrpc/quic"
|
||||
)
|
||||
|
||||
var (
|
||||
testTLSServerConfig = quicpogs.GenerateTLSConfig()
|
||||
testTLSServerConfig = GenerateTLSConfig()
|
||||
testQUICConfig = &quic.Config{
|
||||
KeepAlivePeriod: 5 * time.Second,
|
||||
EnableDatagrams: true,
|
||||
@@ -50,16 +56,16 @@ func TestQUICServer(t *testing.T) {
|
||||
var tests = []struct {
|
||||
desc string
|
||||
dest string
|
||||
connectionType quicpogs.ConnectionType
|
||||
metadata []quicpogs.Metadata
|
||||
connectionType pogs.ConnectionType
|
||||
metadata []pogs.Metadata
|
||||
message []byte
|
||||
expectedResponse []byte
|
||||
}{
|
||||
{
|
||||
desc: "test http proxy",
|
||||
dest: "/ok",
|
||||
connectionType: quicpogs.ConnectionTypeHTTP,
|
||||
metadata: []quicpogs.Metadata{
|
||||
connectionType: pogs.ConnectionTypeHTTP,
|
||||
metadata: []pogs.Metadata{
|
||||
{
|
||||
Key: "HttpHeader:Cf-Ray",
|
||||
Val: "123123123",
|
||||
@@ -78,8 +84,8 @@ func TestQUICServer(t *testing.T) {
|
||||
{
|
||||
desc: "test http body request streaming",
|
||||
dest: "/slow_echo_body",
|
||||
connectionType: quicpogs.ConnectionTypeHTTP,
|
||||
metadata: []quicpogs.Metadata{
|
||||
connectionType: pogs.ConnectionTypeHTTP,
|
||||
metadata: []pogs.Metadata{
|
||||
{
|
||||
Key: "HttpHeader:Cf-Ray",
|
||||
Val: "123123123",
|
||||
@@ -103,8 +109,8 @@ func TestQUICServer(t *testing.T) {
|
||||
{
|
||||
desc: "test ws proxy",
|
||||
dest: "/ws/echo",
|
||||
connectionType: quicpogs.ConnectionTypeWebsocket,
|
||||
metadata: []quicpogs.Metadata{
|
||||
connectionType: pogs.ConnectionTypeWebsocket,
|
||||
metadata: []pogs.Metadata{
|
||||
{
|
||||
Key: "HttpHeader:Cf-Cloudflared-Proxy-Connection-Upgrade",
|
||||
Val: "Websocket",
|
||||
@@ -127,8 +133,8 @@ func TestQUICServer(t *testing.T) {
|
||||
},
|
||||
{
|
||||
desc: "test tcp proxy",
|
||||
connectionType: quicpogs.ConnectionTypeTCP,
|
||||
metadata: []quicpogs.Metadata{},
|
||||
connectionType: pogs.ConnectionTypeTCP,
|
||||
metadata: []pogs.Metadata{},
|
||||
message: []byte("Here is some tcp data"),
|
||||
expectedResponse: []byte("Here is some tcp data"),
|
||||
},
|
||||
@@ -175,7 +181,7 @@ type fakeControlStream struct {
|
||||
ControlStreamHandler
|
||||
}
|
||||
|
||||
func (fakeControlStream) ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error {
|
||||
func (fakeControlStream) ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *pogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error {
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
@@ -188,8 +194,8 @@ func quicServer(
|
||||
t *testing.T,
|
||||
listener *quic.Listener,
|
||||
dest string,
|
||||
connectionType quicpogs.ConnectionType,
|
||||
metadata []quicpogs.Metadata,
|
||||
connectionType pogs.ConnectionType,
|
||||
metadata []pogs.Metadata,
|
||||
message []byte,
|
||||
expectedResponse []byte,
|
||||
) {
|
||||
@@ -198,9 +204,9 @@ func quicServer(
|
||||
|
||||
quicStream, err := session.OpenStreamSync(context.Background())
|
||||
require.NoError(t, err)
|
||||
stream := quicpogs.NewSafeStreamCloser(quicStream, defaultQUICTimeout, &log)
|
||||
stream := cfdquic.NewSafeStreamCloser(quicStream, defaultQUICTimeout, &log)
|
||||
|
||||
reqClientStream := quicpogs.RequestClientStream{ReadWriteCloser: stream}
|
||||
reqClientStream := rpcquic.RequestClientStream{ReadWriteCloser: stream}
|
||||
err = reqClientStream.WriteConnectRequestData(dest, connectionType, metadata...)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -265,15 +271,15 @@ func (moc *mockOriginProxyWithRequest) ProxyHTTP(w ResponseWriter, tr *tracing.T
|
||||
func TestBuildHTTPRequest(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
connectRequest *quicpogs.ConnectRequest
|
||||
connectRequest *pogs.ConnectRequest
|
||||
body io.ReadCloser
|
||||
req *http.Request
|
||||
}{
|
||||
{
|
||||
name: "check if http.Request is built correctly with content length",
|
||||
connectRequest: &quicpogs.ConnectRequest{
|
||||
connectRequest: &pogs.ConnectRequest{
|
||||
Dest: "http://test.com",
|
||||
Metadata: []quicpogs.Metadata{
|
||||
Metadata: []pogs.Metadata{
|
||||
{
|
||||
Key: "HttpHeader:Cf-Cloudflared-Proxy-Connection-Upgrade",
|
||||
Val: "Websocket",
|
||||
@@ -317,9 +323,9 @@ func TestBuildHTTPRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "if content length isn't part of request headers, then it's not set",
|
||||
connectRequest: &quicpogs.ConnectRequest{
|
||||
connectRequest: &pogs.ConnectRequest{
|
||||
Dest: "http://test.com",
|
||||
Metadata: []quicpogs.Metadata{
|
||||
Metadata: []pogs.Metadata{
|
||||
{
|
||||
Key: "HttpHeader:Cf-Cloudflared-Proxy-Connection-Upgrade",
|
||||
Val: "Websocket",
|
||||
@@ -358,9 +364,9 @@ func TestBuildHTTPRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "if content length is 0, but transfer-encoding is chunked, body is not nil",
|
||||
connectRequest: &quicpogs.ConnectRequest{
|
||||
connectRequest: &pogs.ConnectRequest{
|
||||
Dest: "http://test.com",
|
||||
Metadata: []quicpogs.Metadata{
|
||||
Metadata: []pogs.Metadata{
|
||||
{
|
||||
Key: "HttpHeader:Another-Header",
|
||||
Val: "Misc",
|
||||
@@ -400,9 +406,9 @@ func TestBuildHTTPRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "if content length is 0, but transfer-encoding is gzip,chunked, body is not nil",
|
||||
connectRequest: &quicpogs.ConnectRequest{
|
||||
connectRequest: &pogs.ConnectRequest{
|
||||
Dest: "http://test.com",
|
||||
Metadata: []quicpogs.Metadata{
|
||||
Metadata: []pogs.Metadata{
|
||||
{
|
||||
Key: "HttpHeader:Another-Header",
|
||||
Val: "Misc",
|
||||
@@ -442,10 +448,10 @@ func TestBuildHTTPRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "if content length is 0, and connect request is a websocket, body is not nil",
|
||||
connectRequest: &quicpogs.ConnectRequest{
|
||||
Type: quicpogs.ConnectionTypeWebsocket,
|
||||
connectRequest: &pogs.ConnectRequest{
|
||||
Type: pogs.ConnectionTypeWebsocket,
|
||||
Dest: "http://test.com",
|
||||
Metadata: []quicpogs.Metadata{
|
||||
Metadata: []pogs.Metadata{
|
||||
{
|
||||
Key: "HttpHeader:Another-Header",
|
||||
Val: "Misc",
|
||||
@@ -617,9 +623,9 @@ func serveSession(ctx context.Context, qc *QUICConnection, edgeQUICSession quic.
|
||||
}()
|
||||
|
||||
// Send a message to the quic session on edge side, it should be deumx to this datagram v2 session
|
||||
muxedPayload, err := quicpogs.SuffixSessionID(sessionID, payload)
|
||||
muxedPayload, err := cfdquic.SuffixSessionID(sessionID, payload)
|
||||
require.NoError(t, err)
|
||||
muxedPayload, err = quicpogs.SuffixType(muxedPayload, quicpogs.DatagramTypeUDP)
|
||||
muxedPayload, err = cfdquic.SuffixType(muxedPayload, cfdquic.DatagramTypeUDP)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = edgeQUICSession.SendDatagram(muxedPayload)
|
||||
@@ -665,7 +671,7 @@ const (
|
||||
closedByTimeout
|
||||
)
|
||||
|
||||
func runRPCServer(ctx context.Context, session quic.Connection, sessionRPCServer tunnelpogs.SessionManager, configRPCServer tunnelpogs.ConfigurationManager, t *testing.T) {
|
||||
func runRPCServer(ctx context.Context, session quic.Connection, sessionRPCServer pogs.SessionManager, configRPCServer pogs.ConfigurationManager, t *testing.T) {
|
||||
stream, err := session.AcceptStream(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -674,13 +680,15 @@ func runRPCServer(ctx context.Context, session quic.Connection, sessionRPCServer
|
||||
stream, err = session.AcceptStream(ctx)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
protocol, err := quicpogs.DetermineProtocol(stream)
|
||||
assert.NoError(t, err)
|
||||
rpcServerStream, err := quicpogs.NewRPCServerStream(stream, protocol)
|
||||
assert.NoError(t, err)
|
||||
|
||||
log := zerolog.New(os.Stdout)
|
||||
err = rpcServerStream.Serve(sessionRPCServer, configRPCServer, &log)
|
||||
ss := rpcquic.NewCloudflaredServer(
|
||||
func(_ context.Context, _ *rpcquic.RequestServerStream) error {
|
||||
return nil
|
||||
},
|
||||
sessionRPCServer,
|
||||
configRPCServer,
|
||||
10*time.Second,
|
||||
)
|
||||
err = ss.Serve(ctx, stream)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -726,7 +734,7 @@ func testQUICConnection(udpListenerAddr net.Addr, t *testing.T, index uint8) *QU
|
||||
fakeControlStream{},
|
||||
&log,
|
||||
nil,
|
||||
5*time.Second,
|
||||
15*time.Second,
|
||||
0*time.Second,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@@ -744,3 +752,27 @@ func (m *mockReaderNoopWriter) Write(p []byte) (n int, err error) {
|
||||
func (m *mockReaderNoopWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateTLSConfig sets up a bare-bones TLS config for a QUIC server
|
||||
func GenerateTLSConfig() *tls.Config {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
NextProtos: []string{"argotunnel"},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
package connection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
type tunnelServerClient struct {
|
||||
client tunnelpogs.TunnelServer_PogsClient
|
||||
transport rpc.Transport
|
||||
}
|
||||
|
||||
// NewTunnelRPCClient creates and returns a new RPC client, which will communicate using a stream on the given muxer.
|
||||
// This method is exported for supervisor to call Authenticate RPC
|
||||
func NewTunnelServerClient(
|
||||
ctx context.Context,
|
||||
stream io.ReadWriteCloser,
|
||||
log *zerolog.Logger,
|
||||
) *tunnelServerClient {
|
||||
transport := tunnelrpc.NewTransportLogger(log, rpc.StreamTransport(stream))
|
||||
conn := rpc.NewConn(
|
||||
transport,
|
||||
tunnelrpc.ConnLog(log),
|
||||
)
|
||||
registrationClient := tunnelpogs.RegistrationServer_PogsClient{Client: conn.Bootstrap(ctx), Conn: conn}
|
||||
return &tunnelServerClient{
|
||||
client: tunnelpogs.TunnelServer_PogsClient{RegistrationServer_PogsClient: registrationClient, Client: conn.Bootstrap(ctx), Conn: conn},
|
||||
transport: transport,
|
||||
}
|
||||
}
|
||||
|
||||
func (tsc *tunnelServerClient) Authenticate(ctx context.Context, classicTunnel *ClassicTunnelProperties, registrationOptions *tunnelpogs.RegistrationOptions) (tunnelpogs.AuthOutcome, error) {
|
||||
authResp, err := tsc.client.Authenticate(ctx, classicTunnel.OriginCert, classicTunnel.Hostname, registrationOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return authResp.Outcome(), nil
|
||||
}
|
||||
|
||||
func (tsc *tunnelServerClient) Close() {
|
||||
// Closing the client will also close the connection
|
||||
_ = tsc.client.Close()
|
||||
_ = tsc.transport.Close()
|
||||
}
|
||||
|
||||
type NamedTunnelRPCClient interface {
|
||||
RegisterConnection(
|
||||
c context.Context,
|
||||
config *NamedTunnelProperties,
|
||||
options *tunnelpogs.ConnectionOptions,
|
||||
connIndex uint8,
|
||||
edgeAddress net.IP,
|
||||
observer *Observer,
|
||||
) (*tunnelpogs.ConnectionDetails, error)
|
||||
SendLocalConfiguration(
|
||||
c context.Context,
|
||||
config []byte,
|
||||
observer *Observer,
|
||||
) error
|
||||
GracefulShutdown(ctx context.Context, gracePeriod time.Duration)
|
||||
Close()
|
||||
}
|
||||
|
||||
type registrationServerClient struct {
|
||||
client tunnelpogs.RegistrationServer_PogsClient
|
||||
transport rpc.Transport
|
||||
}
|
||||
|
||||
func newRegistrationRPCClient(
|
||||
ctx context.Context,
|
||||
stream io.ReadWriteCloser,
|
||||
log *zerolog.Logger,
|
||||
) NamedTunnelRPCClient {
|
||||
transport := tunnelrpc.NewTransportLogger(log, rpc.StreamTransport(stream))
|
||||
conn := rpc.NewConn(
|
||||
transport,
|
||||
tunnelrpc.ConnLog(log),
|
||||
)
|
||||
return ®istrationServerClient{
|
||||
client: tunnelpogs.RegistrationServer_PogsClient{Client: conn.Bootstrap(ctx), Conn: conn},
|
||||
transport: transport,
|
||||
}
|
||||
}
|
||||
|
||||
func (rsc *registrationServerClient) RegisterConnection(
|
||||
ctx context.Context,
|
||||
properties *NamedTunnelProperties,
|
||||
options *tunnelpogs.ConnectionOptions,
|
||||
connIndex uint8,
|
||||
edgeAddress net.IP,
|
||||
observer *Observer,
|
||||
) (*tunnelpogs.ConnectionDetails, error) {
|
||||
conn, err := rsc.client.RegisterConnection(
|
||||
ctx,
|
||||
properties.Credentials.Auth(),
|
||||
properties.Credentials.TunnelID,
|
||||
connIndex,
|
||||
options,
|
||||
)
|
||||
if err != nil {
|
||||
if err.Error() == DuplicateConnectionError {
|
||||
observer.metrics.regFail.WithLabelValues("dup_edge_conn", "registerConnection").Inc()
|
||||
return nil, errDuplicationConnection
|
||||
}
|
||||
observer.metrics.regFail.WithLabelValues("server_error", "registerConnection").Inc()
|
||||
return nil, serverRegistrationErrorFromRPC(err)
|
||||
}
|
||||
|
||||
observer.metrics.regSuccess.WithLabelValues("registerConnection").Inc()
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (rsc *registrationServerClient) SendLocalConfiguration(ctx context.Context, config []byte, observer *Observer) (err error) {
|
||||
observer.metrics.localConfigMetrics.pushes.Inc()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
observer.metrics.localConfigMetrics.pushesErrors.Inc()
|
||||
}
|
||||
}()
|
||||
|
||||
return rsc.client.SendLocalConfiguration(ctx, config)
|
||||
}
|
||||
|
||||
func (rsc *registrationServerClient) GracefulShutdown(ctx context.Context, gracePeriod time.Duration) {
|
||||
ctx, cancel := context.WithTimeout(ctx, gracePeriod)
|
||||
defer cancel()
|
||||
_ = rsc.client.UnregisterConnection(ctx)
|
||||
}
|
||||
|
||||
func (rsc *registrationServerClient) Close() {
|
||||
// Closing the client will also close the connection
|
||||
_ = rsc.client.Close()
|
||||
// Closing the transport also closes the stream
|
||||
_ = rsc.transport.Close()
|
||||
}
|
||||
|
||||
type rpcName string
|
||||
|
||||
const (
|
||||
register rpcName = "register"
|
||||
reconnect rpcName = "reconnect"
|
||||
unregister rpcName = "unregister"
|
||||
authenticate rpcName = " authenticate"
|
||||
)
|
||||
@@ -15,7 +15,7 @@ var (
|
||||
Name: "active_sessions",
|
||||
Help: "Concurrent count of UDP sessions that are being proxied to any origin",
|
||||
})
|
||||
totalUDPSessions = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
totalUDPSessions = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: "udp",
|
||||
Name: "total_sessions",
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.21.5 as builder
|
||||
FROM golang:1.22.2 as builder
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0
|
||||
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/cloudflare/cloudflared
|
||||
|
||||
go 1.21
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/coredns/coredns v1.10.0
|
||||
@@ -16,7 +16,7 @@ require (
|
||||
github.com/gobwas/ws v1.0.4
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
@@ -27,21 +27,21 @@ require (
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/quic-go/quic-go v0.42.0
|
||||
github.com/rs/zerolog v1.20.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
go.opentelemetry.io/contrib/propagators v0.22.0
|
||||
go.opentelemetry.io/otel v1.21.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0
|
||||
go.opentelemetry.io/otel/sdk v1.21.0
|
||||
go.opentelemetry.io/otel/trace v1.21.0
|
||||
go.opentelemetry.io/proto/otlp v1.0.0
|
||||
go.opentelemetry.io/otel v1.26.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0
|
||||
go.opentelemetry.io/otel/sdk v1.26.0
|
||||
go.opentelemetry.io/otel/trace v1.26.0
|
||||
go.opentelemetry.io/proto/otlp v1.2.0
|
||||
go.uber.org/automaxprocs v1.4.0
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/net v0.21.0
|
||||
golang.org/x/sync v0.4.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/term v0.18.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/term v0.20.0
|
||||
google.golang.org/protobuf v1.34.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
nhooyr.io/websocket v1.8.7
|
||||
@@ -61,14 +61,14 @@ require (
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
@@ -83,17 +83,17 @@ require (
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/oauth2 v0.13.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/oauth2 v0.17.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/tools v0.9.1 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect
|
||||
google.golang.org/grpc v1.60.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
google.golang.org/grpc v1.63.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
|
||||
@@ -119,8 +119,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
@@ -152,8 +152,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
|
||||
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -180,8 +178,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
@@ -212,15 +210,15 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJY
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
@@ -344,8 +342,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
|
||||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
@@ -366,19 +364,19 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/contrib/propagators v0.22.0 h1:KGdv58M2//veiYLIhb31mofaI2LgkIPXXAZVeYVyfd8=
|
||||
go.opentelemetry.io/contrib/propagators v0.22.0/go.mod h1:xGOuXr6lLIF9BXipA4pm6UuOSI0M98U6tsI3khbOiwU=
|
||||
go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM=
|
||||
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
|
||||
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
|
||||
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
|
||||
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs=
|
||||
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
|
||||
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
|
||||
go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8=
|
||||
go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs=
|
||||
go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4=
|
||||
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
|
||||
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
|
||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
||||
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
|
||||
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
@@ -390,8 +388,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -463,8 +461,8 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -472,8 +470,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
|
||||
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
|
||||
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
|
||||
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -485,8 +483,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -533,12 +531,12 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -547,8 +545,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -656,12 +654,12 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0=
|
||||
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY=
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -674,8 +672,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.60.0 h1:6FQAR0kM31P6MRdeluor2w2gPaS4SVNrD/DNTxrQ15k=
|
||||
google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
||||
google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8=
|
||||
google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -688,8 +686,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
namespace = "cloudflared"
|
||||
)
|
||||
|
||||
var (
|
||||
icmpRequests = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: "icmp",
|
||||
Name: "total_requests",
|
||||
Help: "Total count of ICMP requests that have been proxied to any origin",
|
||||
})
|
||||
icmpReplies = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: "icmp",
|
||||
Name: "total_replies",
|
||||
Help: "Total count of ICMP replies that have been proxied from any origin",
|
||||
})
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(
|
||||
icmpRequests,
|
||||
icmpReplies,
|
||||
)
|
||||
}
|
||||
|
||||
func incrementICMPRequest() {
|
||||
icmpRequests.Inc()
|
||||
}
|
||||
|
||||
func incrementICMPReply() {
|
||||
icmpReplies.Inc()
|
||||
}
|
||||
@@ -105,6 +105,7 @@ func isEchoReply(msg *icmp.Message) bool {
|
||||
}
|
||||
|
||||
func observeICMPRequest(logger *zerolog.Logger, span trace.Span, src string, dst string, echoID int, seq int) {
|
||||
incrementICMPRequest()
|
||||
logger.Debug().
|
||||
Str("src", src).
|
||||
Str("dst", dst).
|
||||
@@ -118,6 +119,7 @@ func observeICMPRequest(logger *zerolog.Logger, span trace.Span, src string, dst
|
||||
}
|
||||
|
||||
func observeICMPReply(logger *zerolog.Logger, span trace.Span, dst string, echoID int, seq int) {
|
||||
incrementICMPReply()
|
||||
logger.Debug().Str("dst", dst).Int("echoID", echoID).Int("seq", seq).Msg("Sent ICMP reply to edge")
|
||||
span.SetAttributes(
|
||||
attribute.String("dst", dst),
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
"github.com/cloudflare/cloudflared/proxy"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// Orchestrator manages configurations, so they can be updatable during runtime
|
||||
@@ -32,7 +32,7 @@ type Orchestrator struct {
|
||||
internalRules []ingress.Rule
|
||||
// cloudflared Configuration
|
||||
config *Config
|
||||
tags []tunnelpogs.Tag
|
||||
tags []pogs.Tag
|
||||
log *zerolog.Logger
|
||||
|
||||
// orchestrator must not handle any more updates after shutdownC is closed
|
||||
@@ -43,7 +43,7 @@ type Orchestrator struct {
|
||||
|
||||
func NewOrchestrator(ctx context.Context,
|
||||
config *Config,
|
||||
tags []tunnelpogs.Tag,
|
||||
tags []pogs.Tag,
|
||||
internalRules []ingress.Rule,
|
||||
log *zerolog.Logger) (*Orchestrator, error) {
|
||||
o := &Orchestrator{
|
||||
@@ -65,7 +65,7 @@ func NewOrchestrator(ctx context.Context,
|
||||
}
|
||||
|
||||
// UpdateConfig creates a new proxy with the new ingress rules
|
||||
func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.UpdateConfigurationResponse {
|
||||
func (o *Orchestrator) UpdateConfig(version int32, config []byte) *pogs.UpdateConfigurationResponse {
|
||||
o.lock.Lock()
|
||||
defer o.lock.Unlock()
|
||||
|
||||
@@ -74,7 +74,7 @@ func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.Up
|
||||
Int32("current_version", o.currentVersion).
|
||||
Int32("received_version", version).
|
||||
Msg("Current version is equal or newer than received version")
|
||||
return &tunnelpogs.UpdateConfigurationResponse{
|
||||
return &pogs.UpdateConfigurationResponse{
|
||||
LastAppliedVersion: o.currentVersion,
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.Up
|
||||
Int32("version", version).
|
||||
Str("config", string(config)).
|
||||
Msgf("Failed to deserialize new configuration")
|
||||
return &tunnelpogs.UpdateConfigurationResponse{
|
||||
return &pogs.UpdateConfigurationResponse{
|
||||
LastAppliedVersion: o.currentVersion,
|
||||
Err: err,
|
||||
}
|
||||
@@ -95,7 +95,7 @@ func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.Up
|
||||
Int32("version", version).
|
||||
Str("config", string(config)).
|
||||
Msgf("Failed to update ingress")
|
||||
return &tunnelpogs.UpdateConfigurationResponse{
|
||||
return &pogs.UpdateConfigurationResponse{
|
||||
LastAppliedVersion: o.currentVersion,
|
||||
Err: err,
|
||||
}
|
||||
@@ -107,7 +107,7 @@ func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.Up
|
||||
Str("config", string(config)).
|
||||
Msg("Updated to new configuration")
|
||||
configVersion.Set(float64(version))
|
||||
return &tunnelpogs.UpdateConfigurationResponse{
|
||||
return &pogs.UpdateConfigurationResponse{
|
||||
LastAppliedVersion: o.currentVersion,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ import (
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
"github.com/cloudflare/cloudflared/management"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
var (
|
||||
testLogger = zerolog.Nop()
|
||||
testTags = []tunnelpogs.Tag{
|
||||
testTags = []pogs.Tag{
|
||||
{
|
||||
Name: "package",
|
||||
Value: "orchestration",
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@ import (
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
"github.com/cloudflare/cloudflared/stream"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -33,7 +33,7 @@ type Proxy struct {
|
||||
ingressRules ingress.Ingress
|
||||
warpRouting *ingress.WarpRoutingService
|
||||
management *ingress.ManagementService
|
||||
tags []tunnelpogs.Tag
|
||||
tags []pogs.Tag
|
||||
log *zerolog.Logger
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ type Proxy struct {
|
||||
func NewOriginProxy(
|
||||
ingressRules ingress.Ingress,
|
||||
warpRouting ingress.WarpRoutingConfig,
|
||||
tags []tunnelpogs.Tag,
|
||||
tags []pogs.Tag,
|
||||
writeTimeout time.Duration,
|
||||
log *zerolog.Logger,
|
||||
) *Proxy {
|
||||
|
||||
+2
-2
@@ -30,11 +30,11 @@ import (
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
var (
|
||||
testTags = []tunnelpogs.Tag{{Name: "Name", Value: "value"}}
|
||||
testTags = []pogs.Tag{{Name: "Name", Value: "value"}}
|
||||
noWarpRouting = ingress.WarpRoutingConfig{}
|
||||
testWarpRouting = ingress.WarpRoutingConfig{
|
||||
ConnectTimeout: config.CustomDuration{Duration: time.Second},
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package quic
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
HandshakeIdleTimeout = 5 * time.Second
|
||||
MaxIdleTimeout = 5 * time.Second
|
||||
MaxIdlePingPeriod = 1 * time.Second
|
||||
|
||||
// MaxIncomingStreams is 2^60, which is the maximum supported value by Quic-Go
|
||||
MaxIncomingStreams = 1 << 60
|
||||
)
|
||||
@@ -1,274 +0,0 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// ProtocolSignature defines the first 6 bytes of the stream, which is used to distinguish the type of stream. It
|
||||
// ensures whoever performs a handshake does not write data before writing the metadata.
|
||||
type ProtocolSignature [6]byte
|
||||
|
||||
var (
|
||||
// DataStreamProtocolSignature is a custom protocol signature for data stream
|
||||
DataStreamProtocolSignature = ProtocolSignature{0x0A, 0x36, 0xCD, 0x12, 0xA1, 0x3E}
|
||||
|
||||
// RPCStreamProtocolSignature is a custom protocol signature for RPC stream
|
||||
RPCStreamProtocolSignature = ProtocolSignature{0x52, 0xBB, 0x82, 0x5C, 0xDB, 0x65}
|
||||
)
|
||||
|
||||
type protocolVersion string
|
||||
|
||||
const (
|
||||
protocolV1 protocolVersion = "01"
|
||||
|
||||
protocolVersionLength = 2
|
||||
|
||||
HandshakeIdleTimeout = 5 * time.Second
|
||||
MaxIdleTimeout = 5 * time.Second
|
||||
MaxIdlePingPeriod = 1 * time.Second
|
||||
|
||||
// MaxIncomingStreams is 2^60, which is the maximum supported value by Quic-Go
|
||||
MaxIncomingStreams = 1 << 60
|
||||
)
|
||||
|
||||
// RequestServerStream is a stream to serve requests
|
||||
type RequestServerStream struct {
|
||||
io.ReadWriteCloser
|
||||
}
|
||||
|
||||
func NewRequestServerStream(stream io.ReadWriteCloser, signature ProtocolSignature) (*RequestServerStream, error) {
|
||||
if signature != DataStreamProtocolSignature {
|
||||
return nil, fmt.Errorf("RequestClientStream can only be created from data stream")
|
||||
}
|
||||
return &RequestServerStream{stream}, nil
|
||||
}
|
||||
|
||||
// ReadConnectRequestData reads the handshake data from a QUIC stream.
|
||||
func (rss *RequestServerStream) ReadConnectRequestData() (*ConnectRequest, error) {
|
||||
// This is a NO-OP for now. We could cause a branching if we wanted to use multiple versions.
|
||||
if _, err := readVersion(rss); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, err := capnp.NewDecoder(rss).Decode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &ConnectRequest{}
|
||||
if err := r.fromPogs(msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// WriteConnectResponseData writes response to a QUIC stream.
|
||||
func (rss *RequestServerStream) WriteConnectResponseData(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 := writeDataStreamPreamble(rss); err != nil {
|
||||
return err
|
||||
}
|
||||
return capnp.NewEncoder(rss).Encode(msg)
|
||||
}
|
||||
|
||||
type RequestClientStream struct {
|
||||
io.ReadWriteCloser
|
||||
}
|
||||
|
||||
// WriteConnectRequestData writes requestMeta to a stream.
|
||||
func (rcs *RequestClientStream) WriteConnectRequestData(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 := writeDataStreamPreamble(rcs); err != nil {
|
||||
return err
|
||||
}
|
||||
return capnp.NewEncoder(rcs).Encode(msg)
|
||||
}
|
||||
|
||||
// ReadConnectResponseData reads the response to a RequestMeta in a stream.
|
||||
func (rcs *RequestClientStream) ReadConnectResponseData() (*ConnectResponse, error) {
|
||||
signature, err := DetermineProtocol(rcs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if signature != DataStreamProtocolSignature {
|
||||
return nil, fmt.Errorf("wrong protocol signature %v", signature)
|
||||
}
|
||||
|
||||
// This is a NO-OP for now. We could cause a branching if we wanted to use multiple versions.
|
||||
if _, err := readVersion(rcs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, err := capnp.NewDecoder(rcs).Decode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &ConnectResponse{}
|
||||
if err := r.fromPogs(msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// RPCServerStream is a stream to serve RPCs. It is closed when the RPC client is done
|
||||
type RPCServerStream struct {
|
||||
io.ReadWriteCloser
|
||||
}
|
||||
|
||||
func NewRPCServerStream(stream io.ReadWriteCloser, protocol ProtocolSignature) (*RPCServerStream, error) {
|
||||
if protocol != RPCStreamProtocolSignature {
|
||||
return nil, fmt.Errorf("RPCStream can only be created from rpc stream")
|
||||
}
|
||||
return &RPCServerStream{stream}, nil
|
||||
}
|
||||
|
||||
func (s *RPCServerStream) Serve(sessionManager tunnelpogs.SessionManager, configManager tunnelpogs.ConfigurationManager, logger *zerolog.Logger) error {
|
||||
// RPC logs are very robust, create a new logger that only logs error to reduce noise
|
||||
rpcLogger := logger.Level(zerolog.ErrorLevel)
|
||||
rpcTransport := tunnelrpc.NewTransportLogger(&rpcLogger, rpc.StreamTransport(s))
|
||||
defer rpcTransport.Close()
|
||||
|
||||
main := tunnelpogs.CloudflaredServer_ServerToClient(sessionManager, configManager)
|
||||
rpcConn := rpc.NewConn(
|
||||
rpcTransport,
|
||||
rpc.MainInterface(main.Client),
|
||||
tunnelrpc.ConnLog(&rpcLogger),
|
||||
)
|
||||
defer rpcConn.Close()
|
||||
|
||||
return rpcConn.Wait()
|
||||
}
|
||||
|
||||
func DetermineProtocol(stream io.Reader) (ProtocolSignature, error) {
|
||||
signature, err := readSignature(stream)
|
||||
if err != nil {
|
||||
return ProtocolSignature{}, err
|
||||
}
|
||||
switch signature {
|
||||
case DataStreamProtocolSignature:
|
||||
return DataStreamProtocolSignature, nil
|
||||
case RPCStreamProtocolSignature:
|
||||
return RPCStreamProtocolSignature, nil
|
||||
default:
|
||||
return ProtocolSignature{}, fmt.Errorf("unknown signature %v", signature)
|
||||
}
|
||||
}
|
||||
|
||||
func writeDataStreamPreamble(stream io.Writer) error {
|
||||
if err := writeSignature(stream, DataStreamProtocolSignature); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeVersion(stream)
|
||||
}
|
||||
|
||||
func writeVersion(stream io.Writer) error {
|
||||
_, err := stream.Write([]byte(protocolV1)[:protocolVersionLength])
|
||||
return err
|
||||
}
|
||||
|
||||
func readVersion(stream io.Reader) (string, error) {
|
||||
version := make([]byte, protocolVersionLength)
|
||||
_, err := stream.Read(version)
|
||||
return string(version), err
|
||||
}
|
||||
|
||||
func readSignature(stream io.Reader) (ProtocolSignature, error) {
|
||||
var signature ProtocolSignature
|
||||
if _, err := io.ReadFull(stream, signature[:]); err != nil {
|
||||
return ProtocolSignature{}, err
|
||||
}
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
func writeSignature(stream io.Writer, signature ProtocolSignature) error {
|
||||
_, err := stream.Write(signature[:])
|
||||
return err
|
||||
}
|
||||
|
||||
// RPCClientStream is a stream to call methods of SessionManager
|
||||
type RPCClientStream struct {
|
||||
client tunnelpogs.CloudflaredServer_PogsClient
|
||||
transport rpc.Transport
|
||||
|
||||
// Time we wait for the server to respond to a request before we close the connection.
|
||||
rpcUnregisterUDPSessionDeadline time.Duration
|
||||
}
|
||||
|
||||
func NewRPCClientStream(ctx context.Context, stream io.ReadWriteCloser, rpcUnregisterUDPSessionDeadline time.Duration, logger *zerolog.Logger) (*RPCClientStream, error) {
|
||||
n, err := stream.Write(RPCStreamProtocolSignature[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n != len(RPCStreamProtocolSignature) {
|
||||
return nil, fmt.Errorf("expect to write %d bytes for RPC stream protocol signature, wrote %d", len(RPCStreamProtocolSignature), n)
|
||||
}
|
||||
transport := tunnelrpc.NewTransportLogger(logger, rpc.StreamTransport(stream))
|
||||
conn := rpc.NewConn(
|
||||
transport,
|
||||
tunnelrpc.ConnLog(logger),
|
||||
)
|
||||
return &RPCClientStream{
|
||||
client: tunnelpogs.NewCloudflaredServer_PogsClient(conn.Bootstrap(ctx), conn),
|
||||
transport: transport,
|
||||
rpcUnregisterUDPSessionDeadline: rpcUnregisterUDPSessionDeadline,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rcs *RPCClientStream) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfterHint time.Duration, traceContext string) (*tunnelpogs.RegisterUdpSessionResponse, error) {
|
||||
return rcs.client.RegisterUdpSession(ctx, sessionID, dstIP, dstPort, closeIdleAfterHint, traceContext)
|
||||
}
|
||||
|
||||
func (rcs *RPCClientStream) UnregisterUdpSession(ctx context.Context, sessionID uuid.UUID, message string) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, rcs.rpcUnregisterUDPSessionDeadline)
|
||||
defer cancel()
|
||||
return rcs.client.UnregisterUdpSession(ctx, sessionID, message)
|
||||
}
|
||||
|
||||
func (rcs *RPCClientStream) UpdateConfiguration(ctx context.Context, version int32, config []byte) (*tunnelpogs.UpdateConfigurationResponse, error) {
|
||||
return rcs.client.UpdateConfiguration(ctx, version, config)
|
||||
}
|
||||
|
||||
func (rcs *RPCClientStream) Close() {
|
||||
_ = rcs.client.Close()
|
||||
_ = rcs.transport.Close()
|
||||
}
|
||||
@@ -2,8 +2,13 @@ package quic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -147,3 +152,27 @@ func serverRoundTrip(t *testing.T, stream io.ReadWriteCloser, mustWork bool) {
|
||||
}
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// GenerateTLSConfig sets up a bare-bones TLS config for a QUIC server
|
||||
func GenerateTLSConfig() *tls.Config {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
NextProtos: []string{"argotunnel"},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// GenerateTLSConfig sets up a bare-bones TLS config for a QUIC server
|
||||
func GenerateTLSConfig() *tls.Config {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
NextProtos: []string{"argotunnel"},
|
||||
}
|
||||
}
|
||||
+32
-21
@@ -6,17 +6,16 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultBaseTime time.Duration = time.Second
|
||||
)
|
||||
|
||||
// Redeclare time functions so they can be overridden in tests.
|
||||
type clock struct {
|
||||
type Clock struct {
|
||||
Now func() time.Time
|
||||
After func(d time.Duration) <-chan time.Time
|
||||
}
|
||||
|
||||
var Clock = clock{
|
||||
Now: time.Now,
|
||||
After: time.After,
|
||||
}
|
||||
|
||||
// BackoffHandler manages exponential backoff and limits the maximum number of retries.
|
||||
// The base time period is 1 second, doubling with each retry.
|
||||
// After initial success, a grace period can be set to reset the backoff timer if
|
||||
@@ -25,15 +24,26 @@ var Clock = clock{
|
||||
type BackoffHandler struct {
|
||||
// MaxRetries sets the maximum number of retries to perform. The default value
|
||||
// of 0 disables retry completely.
|
||||
MaxRetries uint
|
||||
maxRetries uint
|
||||
// RetryForever caps the exponential backoff period according to MaxRetries
|
||||
// but allows you to retry indefinitely.
|
||||
RetryForever bool
|
||||
retryForever bool
|
||||
// BaseTime sets the initial backoff period.
|
||||
BaseTime time.Duration
|
||||
baseTime time.Duration
|
||||
|
||||
retries uint
|
||||
resetDeadline time.Time
|
||||
|
||||
Clock Clock
|
||||
}
|
||||
|
||||
func NewBackoff(maxRetries uint, baseTime time.Duration, retryForever bool) BackoffHandler {
|
||||
return BackoffHandler{
|
||||
maxRetries: maxRetries,
|
||||
baseTime: baseTime,
|
||||
retryForever: retryForever,
|
||||
Clock: Clock{Now: time.Now, After: time.After},
|
||||
}
|
||||
}
|
||||
|
||||
func (b BackoffHandler) GetMaxBackoffDuration(ctx context.Context) (time.Duration, bool) {
|
||||
@@ -44,11 +54,11 @@ func (b BackoffHandler) GetMaxBackoffDuration(ctx context.Context) (time.Duratio
|
||||
return time.Duration(0), false
|
||||
default:
|
||||
}
|
||||
if !b.resetDeadline.IsZero() && Clock.Now().After(b.resetDeadline) {
|
||||
if !b.resetDeadline.IsZero() && b.Clock.Now().After(b.resetDeadline) {
|
||||
// b.retries would be set to 0 at this point
|
||||
return time.Second, true
|
||||
}
|
||||
if b.retries >= b.MaxRetries && !b.RetryForever {
|
||||
if b.retries >= b.maxRetries && !b.retryForever {
|
||||
return time.Duration(0), false
|
||||
}
|
||||
maxTimeToWait := b.GetBaseTime() * 1 << (b.retries + 1)
|
||||
@@ -58,12 +68,12 @@ func (b BackoffHandler) GetMaxBackoffDuration(ctx context.Context) (time.Duratio
|
||||
// BackoffTimer returns a channel that sends the current time when the exponential backoff timeout expires.
|
||||
// Returns nil if the maximum number of retries have been used.
|
||||
func (b *BackoffHandler) BackoffTimer() <-chan time.Time {
|
||||
if !b.resetDeadline.IsZero() && Clock.Now().After(b.resetDeadline) {
|
||||
if !b.resetDeadline.IsZero() && b.Clock.Now().After(b.resetDeadline) {
|
||||
b.retries = 0
|
||||
b.resetDeadline = time.Time{}
|
||||
}
|
||||
if b.retries >= b.MaxRetries {
|
||||
if !b.RetryForever {
|
||||
if b.retries >= b.maxRetries {
|
||||
if !b.retryForever {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
@@ -71,7 +81,7 @@ func (b *BackoffHandler) BackoffTimer() <-chan time.Time {
|
||||
}
|
||||
maxTimeToWait := time.Duration(b.GetBaseTime() * 1 << (b.retries))
|
||||
timeToWait := time.Duration(rand.Int63n(maxTimeToWait.Nanoseconds()))
|
||||
return Clock.After(timeToWait)
|
||||
return b.Clock.After(timeToWait)
|
||||
}
|
||||
|
||||
// Backoff is used to wait according to exponential backoff. Returns false if the
|
||||
@@ -94,16 +104,16 @@ func (b *BackoffHandler) Backoff(ctx context.Context) bool {
|
||||
func (b *BackoffHandler) SetGracePeriod() time.Duration {
|
||||
maxTimeToWait := b.GetBaseTime() * 2 << (b.retries + 1)
|
||||
timeToWait := time.Duration(rand.Int63n(maxTimeToWait.Nanoseconds()))
|
||||
b.resetDeadline = Clock.Now().Add(timeToWait)
|
||||
b.resetDeadline = b.Clock.Now().Add(timeToWait)
|
||||
|
||||
return timeToWait
|
||||
}
|
||||
|
||||
func (b BackoffHandler) GetBaseTime() time.Duration {
|
||||
if b.BaseTime == 0 {
|
||||
return time.Second
|
||||
if b.baseTime == 0 {
|
||||
return DefaultBaseTime
|
||||
}
|
||||
return b.BaseTime
|
||||
return b.baseTime
|
||||
}
|
||||
|
||||
// Retries returns the number of retries consumed so far.
|
||||
@@ -112,9 +122,10 @@ func (b *BackoffHandler) Retries() int {
|
||||
}
|
||||
|
||||
func (b *BackoffHandler) ReachedMaxRetries() bool {
|
||||
return b.retries == b.MaxRetries
|
||||
return b.retries == b.maxRetries
|
||||
}
|
||||
|
||||
func (b *BackoffHandler) ResetNow() {
|
||||
b.resetDeadline = time.Now()
|
||||
b.resetDeadline = b.Clock.Now()
|
||||
b.retries = 0
|
||||
}
|
||||
|
||||
@@ -13,10 +13,9 @@ func immediateTimeAfter(time.Duration) <-chan time.Time {
|
||||
}
|
||||
|
||||
func TestBackoffRetries(t *testing.T) {
|
||||
// make backoff return immediately
|
||||
Clock.After = immediateTimeAfter
|
||||
ctx := context.Background()
|
||||
backoff := BackoffHandler{MaxRetries: 3}
|
||||
// make backoff return immediately
|
||||
backoff := BackoffHandler{maxRetries: 3, Clock: Clock{time.Now, immediateTimeAfter}}
|
||||
if !backoff.Backoff(ctx) {
|
||||
t.Fatalf("backoff failed immediately")
|
||||
}
|
||||
@@ -32,10 +31,10 @@ func TestBackoffRetries(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBackoffCancel(t *testing.T) {
|
||||
// prevent backoff from returning normally
|
||||
Clock.After = func(time.Duration) <-chan time.Time { return make(chan time.Time) }
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
backoff := BackoffHandler{MaxRetries: 3}
|
||||
// prevent backoff from returning normally
|
||||
after := func(time.Duration) <-chan time.Time { return make(chan time.Time) }
|
||||
backoff := BackoffHandler{maxRetries: 3, Clock: Clock{time.Now, after}}
|
||||
cancelFunc()
|
||||
if backoff.Backoff(ctx) {
|
||||
t.Fatalf("backoff allowed after cancel")
|
||||
@@ -46,13 +45,12 @@ func TestBackoffCancel(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBackoffGracePeriod(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
currentTime := time.Now()
|
||||
// make Clock.Now return whatever we like
|
||||
Clock.Now = func() time.Time { return currentTime }
|
||||
now := func() time.Time { return currentTime }
|
||||
// make backoff return immediately
|
||||
Clock.After = immediateTimeAfter
|
||||
ctx := context.Background()
|
||||
backoff := BackoffHandler{MaxRetries: 1}
|
||||
backoff := BackoffHandler{maxRetries: 1, Clock: Clock{now, immediateTimeAfter}}
|
||||
if !backoff.Backoff(ctx) {
|
||||
t.Fatalf("backoff failed immediately")
|
||||
}
|
||||
@@ -70,10 +68,9 @@ func TestBackoffGracePeriod(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetMaxBackoffDurationRetries(t *testing.T) {
|
||||
// make backoff return immediately
|
||||
Clock.After = immediateTimeAfter
|
||||
ctx := context.Background()
|
||||
backoff := BackoffHandler{MaxRetries: 3}
|
||||
// make backoff return immediately
|
||||
backoff := BackoffHandler{maxRetries: 3, Clock: Clock{time.Now, immediateTimeAfter}}
|
||||
if _, ok := backoff.GetMaxBackoffDuration(ctx); !ok {
|
||||
t.Fatalf("backoff failed immediately")
|
||||
}
|
||||
@@ -95,10 +92,9 @@ func TestGetMaxBackoffDurationRetries(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetMaxBackoffDuration(t *testing.T) {
|
||||
// make backoff return immediately
|
||||
Clock.After = immediateTimeAfter
|
||||
ctx := context.Background()
|
||||
backoff := BackoffHandler{MaxRetries: 3}
|
||||
// make backoff return immediately
|
||||
backoff := BackoffHandler{maxRetries: 3, Clock: Clock{time.Now, immediateTimeAfter}}
|
||||
if duration, ok := backoff.GetMaxBackoffDuration(ctx); !ok || duration > time.Second*2 {
|
||||
t.Fatalf("backoff (%s) didn't return < 2 seconds on first retry", duration)
|
||||
}
|
||||
@@ -117,10 +113,9 @@ func TestGetMaxBackoffDuration(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBackoffRetryForever(t *testing.T) {
|
||||
// make backoff return immediately
|
||||
Clock.After = immediateTimeAfter
|
||||
ctx := context.Background()
|
||||
backoff := BackoffHandler{MaxRetries: 3, RetryForever: true}
|
||||
// make backoff return immediately
|
||||
backoff := BackoffHandler{maxRetries: 3, retryForever: true, Clock: Clock{time.Now, immediateTimeAfter}}
|
||||
if duration, ok := backoff.GetMaxBackoffDuration(ctx); !ok || duration > time.Second*2 {
|
||||
t.Fatalf("backoff (%s) didn't return < 2 seconds on first retry", duration)
|
||||
}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
package supervisor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/cloudflare/cloudflared/retry"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
var (
|
||||
errJWTUnset = errors.New("JWT unset")
|
||||
)
|
||||
|
||||
// reconnectTunnelCredentialManager is invoked by functions in tunnel.go to
|
||||
// get/set parameters for ReconnectTunnel RPC calls.
|
||||
type reconnectCredentialManager struct {
|
||||
mu sync.RWMutex
|
||||
jwt []byte
|
||||
eventDigest map[uint8][]byte
|
||||
connDigest map[uint8][]byte
|
||||
authSuccess prometheus.Counter
|
||||
authFail *prometheus.CounterVec
|
||||
}
|
||||
|
||||
func newReconnectCredentialManager(namespace, subsystem string, haConnections int) *reconnectCredentialManager {
|
||||
authSuccess := prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "tunnel_authenticate_success",
|
||||
Help: "Count of successful tunnel authenticate",
|
||||
},
|
||||
)
|
||||
authFail := prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "tunnel_authenticate_fail",
|
||||
Help: "Count of tunnel authenticate errors by type",
|
||||
},
|
||||
[]string{"error"},
|
||||
)
|
||||
prometheus.MustRegister(authSuccess, authFail)
|
||||
return &reconnectCredentialManager{
|
||||
eventDigest: make(map[uint8][]byte, haConnections),
|
||||
connDigest: make(map[uint8][]byte, haConnections),
|
||||
authSuccess: authSuccess,
|
||||
authFail: authFail,
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *reconnectCredentialManager) ReconnectToken() ([]byte, error) {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
if cm.jwt == nil {
|
||||
return nil, errJWTUnset
|
||||
}
|
||||
return cm.jwt, nil
|
||||
}
|
||||
|
||||
func (cm *reconnectCredentialManager) SetReconnectToken(jwt []byte) {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
cm.jwt = jwt
|
||||
}
|
||||
|
||||
func (cm *reconnectCredentialManager) EventDigest(connID uint8) ([]byte, error) {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
digest, ok := cm.eventDigest[connID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no event digest for connection %v", connID)
|
||||
}
|
||||
return digest, nil
|
||||
}
|
||||
|
||||
func (cm *reconnectCredentialManager) SetEventDigest(connID uint8, digest []byte) {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
cm.eventDigest[connID] = digest
|
||||
}
|
||||
|
||||
func (cm *reconnectCredentialManager) ConnDigest(connID uint8) ([]byte, error) {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
digest, ok := cm.connDigest[connID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no connection digest for connection %v", connID)
|
||||
}
|
||||
return digest, nil
|
||||
}
|
||||
|
||||
func (cm *reconnectCredentialManager) SetConnDigest(connID uint8, digest []byte) {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
cm.connDigest[connID] = digest
|
||||
}
|
||||
|
||||
func (cm *reconnectCredentialManager) RefreshAuth(
|
||||
ctx context.Context,
|
||||
backoff *retry.BackoffHandler,
|
||||
authenticate func(ctx context.Context, numPreviousAttempts int) (tunnelpogs.AuthOutcome, error),
|
||||
) (retryTimer <-chan time.Time, err error) {
|
||||
authOutcome, err := authenticate(ctx, backoff.Retries())
|
||||
if err != nil {
|
||||
cm.authFail.WithLabelValues(err.Error()).Inc()
|
||||
if _, ok := backoff.GetMaxBackoffDuration(ctx); ok {
|
||||
return backoff.BackoffTimer(), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// clear backoff timer
|
||||
backoff.SetGracePeriod()
|
||||
|
||||
switch outcome := authOutcome.(type) {
|
||||
case tunnelpogs.AuthSuccess:
|
||||
cm.SetReconnectToken(outcome.JWT())
|
||||
cm.authSuccess.Inc()
|
||||
return retry.Clock.After(outcome.RefreshAfter()), nil
|
||||
case tunnelpogs.AuthUnknown:
|
||||
duration := outcome.RefreshAfter()
|
||||
cm.authFail.WithLabelValues(outcome.Error()).Inc()
|
||||
return retry.Clock.After(duration), nil
|
||||
case tunnelpogs.AuthFail:
|
||||
cm.authFail.WithLabelValues(outcome.Error()).Inc()
|
||||
return nil, outcome
|
||||
default:
|
||||
err := fmt.Errorf("refresh_auth: Unexpected outcome type %T", authOutcome)
|
||||
cm.authFail.WithLabelValues(err.Error()).Inc()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package supervisor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cloudflare/cloudflared/retry"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
func TestRefreshAuthBackoff(t *testing.T) {
|
||||
rcm := newReconnectCredentialManager(t.Name(), t.Name(), 4)
|
||||
|
||||
var wait time.Duration
|
||||
retry.Clock.After = func(d time.Duration) <-chan time.Time {
|
||||
wait = d
|
||||
return time.After(d)
|
||||
}
|
||||
backoff := &retry.BackoffHandler{MaxRetries: 3}
|
||||
auth := func(ctx context.Context, n int) (tunnelpogs.AuthOutcome, error) {
|
||||
return nil, fmt.Errorf("authentication failure")
|
||||
}
|
||||
|
||||
// authentication failures should consume the backoff
|
||||
for i := uint(0); i < backoff.MaxRetries; i++ {
|
||||
retryChan, err := rcm.RefreshAuth(context.Background(), backoff, auth)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, retryChan)
|
||||
require.Greater(t, wait.Seconds(), 0.0)
|
||||
require.Less(t, wait.Seconds(), float64((1<<(i+1))*time.Second))
|
||||
}
|
||||
retryChan, err := rcm.RefreshAuth(context.Background(), backoff, auth)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, retryChan)
|
||||
|
||||
// now we actually make contact with the remote server
|
||||
_, _ = rcm.RefreshAuth(context.Background(), backoff, func(ctx context.Context, n int) (tunnelpogs.AuthOutcome, error) {
|
||||
return tunnelpogs.NewAuthUnknown(errors.New("auth unknown"), 19), nil
|
||||
})
|
||||
|
||||
// The backoff timer should have been reset. To confirm this, make timeNow
|
||||
// return a value after the backoff timer's grace period
|
||||
retry.Clock.Now = func() time.Time {
|
||||
expectedGracePeriod := time.Duration(time.Second * 2 << backoff.MaxRetries)
|
||||
return time.Now().Add(expectedGracePeriod * 2)
|
||||
}
|
||||
_, ok := backoff.GetMaxBackoffDuration(context.Background())
|
||||
require.True(t, ok)
|
||||
}
|
||||
|
||||
func TestRefreshAuthSuccess(t *testing.T) {
|
||||
rcm := newReconnectCredentialManager(t.Name(), t.Name(), 4)
|
||||
|
||||
var wait time.Duration
|
||||
retry.Clock.After = func(d time.Duration) <-chan time.Time {
|
||||
wait = d
|
||||
return time.After(d)
|
||||
}
|
||||
|
||||
backoff := &retry.BackoffHandler{MaxRetries: 3}
|
||||
auth := func(ctx context.Context, n int) (tunnelpogs.AuthOutcome, error) {
|
||||
return tunnelpogs.NewAuthSuccess([]byte("jwt"), 19), nil
|
||||
}
|
||||
|
||||
retryChan, err := rcm.RefreshAuth(context.Background(), backoff, auth)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, retryChan)
|
||||
assert.Equal(t, 19*time.Hour, wait)
|
||||
|
||||
token, err := rcm.ReconnectToken()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("jwt"), token)
|
||||
}
|
||||
|
||||
func TestRefreshAuthUnknown(t *testing.T) {
|
||||
rcm := newReconnectCredentialManager(t.Name(), t.Name(), 4)
|
||||
|
||||
var wait time.Duration
|
||||
retry.Clock.After = func(d time.Duration) <-chan time.Time {
|
||||
wait = d
|
||||
return time.After(d)
|
||||
}
|
||||
|
||||
backoff := &retry.BackoffHandler{MaxRetries: 3}
|
||||
auth := func(ctx context.Context, n int) (tunnelpogs.AuthOutcome, error) {
|
||||
return tunnelpogs.NewAuthUnknown(errors.New("auth unknown"), 19), nil
|
||||
}
|
||||
|
||||
retryChan, err := rcm.RefreshAuth(context.Background(), backoff, auth)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, retryChan)
|
||||
assert.Equal(t, 19*time.Hour, wait)
|
||||
|
||||
token, err := rcm.ReconnectToken()
|
||||
assert.Equal(t, errJWTUnset, err)
|
||||
assert.Nil(t, token)
|
||||
}
|
||||
|
||||
func TestRefreshAuthFail(t *testing.T) {
|
||||
rcm := newReconnectCredentialManager(t.Name(), t.Name(), 4)
|
||||
|
||||
backoff := &retry.BackoffHandler{MaxRetries: 3}
|
||||
auth := func(ctx context.Context, n int) (tunnelpogs.AuthOutcome, error) {
|
||||
return tunnelpogs.NewAuthFail(errors.New("auth fail")), nil
|
||||
}
|
||||
|
||||
retryChan, err := rcm.RefreshAuth(context.Background(), backoff, auth)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, retryChan)
|
||||
|
||||
token, err := rcm.ReconnectToken()
|
||||
assert.Equal(t, errJWTUnset, err)
|
||||
assert.Nil(t, token)
|
||||
}
|
||||
+14
-20
@@ -49,8 +49,6 @@ type Supervisor struct {
|
||||
log *ConnAwareLogger
|
||||
logTransport *zerolog.Logger
|
||||
|
||||
reconnectCredentialManager *reconnectCredentialManager
|
||||
|
||||
reconnectCh chan ReconnectSignal
|
||||
gracefulShutdownC <-chan struct{}
|
||||
}
|
||||
@@ -76,8 +74,6 @@ func NewSupervisor(config *TunnelConfig, orchestrator *orchestration.Orchestrato
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reconnectCredentialManager := newReconnectCredentialManager(connection.MetricsNamespace, connection.TunnelSubsystem, config.HAConnections)
|
||||
|
||||
tracker := tunnelstate.NewConnTracker(config.Log)
|
||||
log := NewConnAwareLogger(config.Log, tracker, config.Observer)
|
||||
|
||||
@@ -87,7 +83,6 @@ func NewSupervisor(config *TunnelConfig, orchestrator *orchestration.Orchestrato
|
||||
edgeTunnelServer := EdgeTunnelServer{
|
||||
config: config,
|
||||
orchestrator: orchestrator,
|
||||
credentialManager: reconnectCredentialManager,
|
||||
edgeAddrs: edgeIPs,
|
||||
edgeAddrHandler: edgeAddrHandler,
|
||||
edgeBindAddr: edgeBindAddr,
|
||||
@@ -98,18 +93,17 @@ func NewSupervisor(config *TunnelConfig, orchestrator *orchestration.Orchestrato
|
||||
}
|
||||
|
||||
return &Supervisor{
|
||||
config: config,
|
||||
orchestrator: orchestrator,
|
||||
edgeIPs: edgeIPs,
|
||||
edgeTunnelServer: &edgeTunnelServer,
|
||||
tunnelErrors: make(chan tunnelError),
|
||||
tunnelsConnecting: map[int]chan struct{}{},
|
||||
tunnelsProtocolFallback: map[int]*protocolFallback{},
|
||||
log: log,
|
||||
logTransport: config.LogTransport,
|
||||
reconnectCredentialManager: reconnectCredentialManager,
|
||||
reconnectCh: reconnectCh,
|
||||
gracefulShutdownC: gracefulShutdownC,
|
||||
config: config,
|
||||
orchestrator: orchestrator,
|
||||
edgeIPs: edgeIPs,
|
||||
edgeTunnelServer: &edgeTunnelServer,
|
||||
tunnelErrors: make(chan tunnelError),
|
||||
tunnelsConnecting: map[int]chan struct{}{},
|
||||
tunnelsProtocolFallback: map[int]*protocolFallback{},
|
||||
log: log,
|
||||
logTransport: config.LogTransport,
|
||||
reconnectCh: reconnectCh,
|
||||
gracefulShutdownC: gracefulShutdownC,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -138,7 +132,7 @@ func (s *Supervisor) Run(
|
||||
var tunnelsWaiting []int
|
||||
tunnelsActive := s.config.HAConnections
|
||||
|
||||
backoff := retry.BackoffHandler{MaxRetries: s.config.Retries, BaseTime: tunnelRetryDuration, RetryForever: true}
|
||||
backoff := retry.NewBackoff(s.config.Retries, tunnelRetryDuration, true)
|
||||
var backoffTimer <-chan time.Time
|
||||
|
||||
shuttingDown := false
|
||||
@@ -212,7 +206,7 @@ func (s *Supervisor) initialize(
|
||||
s.config.HAConnections = availableAddrs
|
||||
}
|
||||
s.tunnelsProtocolFallback[0] = &protocolFallback{
|
||||
retry.BackoffHandler{MaxRetries: s.config.Retries, RetryForever: true},
|
||||
retry.NewBackoff(s.config.Retries, retry.DefaultBaseTime, true),
|
||||
s.config.ProtocolSelector.Current(),
|
||||
false,
|
||||
}
|
||||
@@ -234,7 +228,7 @@ func (s *Supervisor) initialize(
|
||||
// At least one successful connection, so start the rest
|
||||
for i := 1; i < s.config.HAConnections; i++ {
|
||||
s.tunnelsProtocolFallback[i] = &protocolFallback{
|
||||
retry.BackoffHandler{MaxRetries: s.config.Retries, RetryForever: true},
|
||||
retry.NewBackoff(s.config.Retries, retry.DefaultBaseTime, true),
|
||||
// Set the protocol we know the first tunnel connected with.
|
||||
s.tunnelsProtocolFallback[0].protocol,
|
||||
false,
|
||||
|
||||
+11
-35
@@ -10,7 +10,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/rs/zerolog"
|
||||
@@ -27,8 +26,7 @@ import (
|
||||
quicpogs "github.com/cloudflare/cloudflared/quic"
|
||||
"github.com/cloudflare/cloudflared/retry"
|
||||
"github.com/cloudflare/cloudflared/signal"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/tunnelstate"
|
||||
)
|
||||
|
||||
@@ -49,7 +47,7 @@ type TunnelConfig struct {
|
||||
HAConnections int
|
||||
IsAutoupdated bool
|
||||
LBPool string
|
||||
Tags []tunnelpogs.Tag
|
||||
Tags []pogs.Tag
|
||||
Log *zerolog.Logger
|
||||
LogTransport *zerolog.Logger
|
||||
Observer *connection.Observer
|
||||
@@ -60,47 +58,25 @@ type TunnelConfig struct {
|
||||
|
||||
NeedPQ bool
|
||||
|
||||
NamedTunnel *connection.NamedTunnelProperties
|
||||
NamedTunnel *connection.TunnelProperties
|
||||
ProtocolSelector connection.ProtocolSelector
|
||||
EdgeTLSConfigs map[connection.Protocol]*tls.Config
|
||||
PacketConfig *ingress.GlobalRouterConfig
|
||||
|
||||
UDPUnregisterSessionTimeout time.Duration
|
||||
WriteStreamTimeout time.Duration
|
||||
RPCTimeout time.Duration
|
||||
WriteStreamTimeout time.Duration
|
||||
|
||||
DisableQUICPathMTUDiscovery bool
|
||||
|
||||
FeatureSelector *features.FeatureSelector
|
||||
}
|
||||
|
||||
func (c *TunnelConfig) registrationOptions(connectionID uint8, OriginLocalIP string, uuid uuid.UUID) *tunnelpogs.RegistrationOptions {
|
||||
policy := tunnelrpc.ExistingTunnelPolicy_balance
|
||||
if c.HAConnections <= 1 && c.LBPool == "" {
|
||||
policy = tunnelrpc.ExistingTunnelPolicy_disconnect
|
||||
}
|
||||
return &tunnelpogs.RegistrationOptions{
|
||||
ClientID: c.ClientID,
|
||||
Version: c.ReportedVersion,
|
||||
OS: c.OSArch,
|
||||
ExistingTunnelPolicy: policy,
|
||||
PoolName: c.LBPool,
|
||||
Tags: c.Tags,
|
||||
ConnectionID: connectionID,
|
||||
OriginLocalIP: OriginLocalIP,
|
||||
IsAutoupdated: c.IsAutoupdated,
|
||||
RunFromTerminal: c.RunFromTerminal,
|
||||
CompressionQuality: 0,
|
||||
UUID: uuid.String(),
|
||||
Features: c.SupportedFeatures(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TunnelConfig) connectionOptions(originLocalAddr string, numPreviousAttempts uint8) *tunnelpogs.ConnectionOptions {
|
||||
func (c *TunnelConfig) connectionOptions(originLocalAddr string, numPreviousAttempts uint8) *pogs.ConnectionOptions {
|
||||
// attempt to parse out origin IP, but don't fail since it's informational field
|
||||
host, _, _ := net.SplitHostPort(originLocalAddr)
|
||||
originIP := net.ParseIP(host)
|
||||
|
||||
return &tunnelpogs.ConnectionOptions{
|
||||
return &pogs.ConnectionOptions{
|
||||
Client: c.NamedTunnel.Client,
|
||||
OriginLocalIP: originIP,
|
||||
ReplaceExisting: c.ReplaceExisting,
|
||||
@@ -203,7 +179,6 @@ func (f *ipAddrFallback) ShouldGetNewAddress(connIndex uint8, err error) (needsN
|
||||
type EdgeTunnelServer struct {
|
||||
config *TunnelConfig
|
||||
orchestrator *orchestration.Orchestrator
|
||||
credentialManager *reconnectCredentialManager
|
||||
edgeAddrHandler EdgeAddrHandler
|
||||
edgeAddrs *edgediscovery.Edge
|
||||
edgeBindAddr net.IP
|
||||
@@ -479,6 +454,7 @@ func (e *EdgeTunnelServer) serveConnection(
|
||||
connIndex,
|
||||
addr.UDP.IP,
|
||||
nil,
|
||||
e.config.RPCTimeout,
|
||||
e.gracefulShutdownC,
|
||||
e.config.GracePeriod,
|
||||
protocol,
|
||||
@@ -531,7 +507,7 @@ func (e *EdgeTunnelServer) serveHTTP2(
|
||||
ctx context.Context,
|
||||
connLog *ConnAwareLogger,
|
||||
tlsServerConn net.Conn,
|
||||
connOptions *tunnelpogs.ConnectionOptions,
|
||||
connOptions *pogs.ConnectionOptions,
|
||||
controlStreamHandler connection.ControlStreamHandler,
|
||||
connIndex uint8,
|
||||
) error {
|
||||
@@ -573,7 +549,7 @@ func (e *EdgeTunnelServer) serveQUIC(
|
||||
ctx context.Context,
|
||||
edgeAddr *net.UDPAddr,
|
||||
connLogger *ConnAwareLogger,
|
||||
connOptions *tunnelpogs.ConnectionOptions,
|
||||
connOptions *pogs.ConnectionOptions,
|
||||
controlStreamHandler connection.ControlStreamHandler,
|
||||
connIndex uint8,
|
||||
) (err error, recoverable bool) {
|
||||
@@ -614,7 +590,7 @@ func (e *EdgeTunnelServer) serveQUIC(
|
||||
controlStreamHandler,
|
||||
connLogger.Logger(),
|
||||
e.config.PacketConfig,
|
||||
e.config.UDPUnregisterSessionTimeout,
|
||||
e.config.RPCTimeout,
|
||||
e.config.WriteStreamTimeout,
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
+21
-15
@@ -24,14 +24,18 @@ func (dmf *dynamicMockFetcher) fetch() edgediscovery.PercentageFetcher {
|
||||
}
|
||||
}
|
||||
|
||||
func immediateTimeAfter(time.Duration) <-chan time.Time {
|
||||
c := make(chan time.Time, 1)
|
||||
c <- time.Now()
|
||||
return c
|
||||
}
|
||||
|
||||
func TestWaitForBackoffFallback(t *testing.T) {
|
||||
maxRetries := uint(3)
|
||||
backoff := retry.BackoffHandler{
|
||||
MaxRetries: maxRetries,
|
||||
BaseTime: time.Millisecond * 10,
|
||||
}
|
||||
backoff := retry.NewBackoff(maxRetries, 40*time.Millisecond, false)
|
||||
backoff.Clock.After = immediateTimeAfter
|
||||
log := zerolog.Nop()
|
||||
resolveTTL := time.Duration(0)
|
||||
resolveTTL := 10 * time.Second
|
||||
mockFetcher := dynamicMockFetcher{
|
||||
protocolPercents: edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}},
|
||||
}
|
||||
@@ -64,21 +68,23 @@ func TestWaitForBackoffFallback(t *testing.T) {
|
||||
}
|
||||
|
||||
// Retry fallback protocol
|
||||
for i := 0; i < int(maxRetries); i++ {
|
||||
protoFallback.BackoffTimer() // simulate retry
|
||||
ok := selectNextProtocol(&log, protoFallback, protocolSelector, nil)
|
||||
assert.True(t, ok)
|
||||
fallback, ok := protocolSelector.Fallback()
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, fallback, protoFallback.protocol)
|
||||
}
|
||||
protoFallback.BackoffTimer() // simulate retry
|
||||
ok := selectNextProtocol(&log, protoFallback, protocolSelector, nil)
|
||||
assert.True(t, ok)
|
||||
fallback, ok := protocolSelector.Fallback()
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, fallback, protoFallback.protocol)
|
||||
assert.Equal(t, connection.HTTP2, protoFallback.protocol)
|
||||
|
||||
currentGlobalProtocol := protocolSelector.Current()
|
||||
assert.Equal(t, initProtocol, currentGlobalProtocol)
|
||||
|
||||
// Simulate max retries again (retries reset after protocol switch)
|
||||
for i := 0; i < int(maxRetries); i++ {
|
||||
protoFallback.BackoffTimer()
|
||||
}
|
||||
// No protocol to fallback, return error
|
||||
protoFallback.BackoffTimer() // simulate retry
|
||||
ok := selectNextProtocol(&log, protoFallback, protocolSelector, nil)
|
||||
ok = selectNextProtocol(&log, protoFallback, protocolSelector, nil)
|
||||
assert.False(t, ok)
|
||||
|
||||
protoFallback.reset()
|
||||
|
||||
+2
-1
@@ -94,9 +94,10 @@ func errDeleteTokenFailed(lockFilePath string) error {
|
||||
// newLock will get a new file lock
|
||||
func newLock(path string) *lock {
|
||||
lockPath := path + ".lock"
|
||||
backoff := retry.NewBackoff(uint(7), retry.DefaultBaseTime, false)
|
||||
return &lock{
|
||||
lockFilePath: lockPath,
|
||||
backoff: &retry.BackoffHandler{MaxRetries: 7},
|
||||
backoff: &backoff,
|
||||
sigHandler: &signalHandler{
|
||||
signals: []os.Signal{syscall.SIGINT, syscall.SIGTERM},
|
||||
},
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Generate go.capnp.out with:
|
||||
# capnp compile -o- go.capnp > go.capnp.out
|
||||
# Must run inside this directory to preserve paths.
|
||||
|
||||
@0xd12a1c51fedd6c88;
|
||||
|
||||
annotation package(file) :Text;
|
||||
annotation import(file) :Text;
|
||||
annotation doc(struct, field, enum) :Text;
|
||||
annotation tag(enumerant) :Text;
|
||||
annotation notag(enumerant) :Void;
|
||||
annotation customtype(field) :Text;
|
||||
annotation name(struct, field, union, enum, enumerant, interface, method, param, annotation, const, group) :Text;
|
||||
|
||||
$package("capnp");
|
||||
@@ -1,43 +0,0 @@
|
||||
package tunnelrpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"golang.org/x/net/trace"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
)
|
||||
|
||||
// ConnLogger wraps a Zerolog Logger for a connection.
|
||||
type ConnLogger struct {
|
||||
Log *zerolog.Logger
|
||||
}
|
||||
|
||||
func (c ConnLogger) Infof(ctx context.Context, format string, args ...interface{}) {
|
||||
c.Log.Info().Msgf(format, args...)
|
||||
}
|
||||
|
||||
func (c ConnLogger) Errorf(ctx context.Context, format string, args ...interface{}) {
|
||||
c.Log.Error().Msgf(format, args...)
|
||||
}
|
||||
|
||||
func ConnLog(log *zerolog.Logger) rpc.ConnOption {
|
||||
return rpc.ConnLog(ConnLogger{log})
|
||||
}
|
||||
|
||||
// ConnTracer wraps a trace.EventLog for a connection.
|
||||
type ConnTracer struct {
|
||||
Events trace.EventLog
|
||||
}
|
||||
|
||||
func (c ConnTracer) Infof(ctx context.Context, format string, args ...interface{}) {
|
||||
c.Events.Printf(format, args...)
|
||||
}
|
||||
|
||||
func (c ConnTracer) Errorf(ctx context.Context, format string, args ...interface{}) {
|
||||
c.Events.Errorf(format, args...)
|
||||
}
|
||||
|
||||
func ConnTrace(events trace.EventLog) rpc.ConnOption {
|
||||
return rpc.ConnLog(ConnTracer{events})
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// Package logtransport provides a transport that logs all of its messages.
|
||||
package tunnelrpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"zombiezen.com/go/capnproto2/encoding/text"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
rpccapnp "zombiezen.com/go/capnproto2/std/capnp/rpc"
|
||||
)
|
||||
|
||||
type transport struct {
|
||||
rpc.Transport
|
||||
log *zerolog.Logger
|
||||
}
|
||||
|
||||
// NewTransportLogger creates a new logger that proxies messages to and from t and
|
||||
// logs them to log. If log is nil, then the log package's default
|
||||
// logger is used.
|
||||
func NewTransportLogger(log *zerolog.Logger, t rpc.Transport) rpc.Transport {
|
||||
return &transport{Transport: t, log: log}
|
||||
}
|
||||
|
||||
func (t *transport) SendMessage(ctx context.Context, msg rpccapnp.Message) error {
|
||||
t.log.Trace().Msgf("rpc tx: %s", formatMsg(msg))
|
||||
return t.Transport.SendMessage(ctx, msg)
|
||||
}
|
||||
|
||||
func (t *transport) RecvMessage(ctx context.Context) (rpccapnp.Message, error) {
|
||||
msg, err := t.Transport.RecvMessage(ctx)
|
||||
if err != nil {
|
||||
t.log.Debug().Msgf("rpc rx error: %s", err)
|
||||
return msg, err
|
||||
}
|
||||
t.log.Trace().Msgf("rpc rx: %s", formatMsg(msg))
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func formatMsg(m rpccapnp.Message) string {
|
||||
var buf bytes.Buffer
|
||||
_ = text.NewEncoder(&buf).Encode(0x91b79f1f808db032, m.Struct)
|
||||
return buf.String()
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
metricsNamespace = "cloudflared"
|
||||
rpcSubsystem = "rpc"
|
||||
)
|
||||
|
||||
// CloudflaredServer operation labels
|
||||
// CloudflaredServer is an extension of SessionManager with additional methods, but it's helpful
|
||||
// to visualize it separately in the metrics since they are technically different client/servers.
|
||||
const (
|
||||
Cloudflared = "cloudflared"
|
||||
)
|
||||
|
||||
// ConfigurationManager operation labels
|
||||
const (
|
||||
ConfigurationManager = "config"
|
||||
|
||||
OperationUpdateConfiguration = "update_configuration"
|
||||
)
|
||||
|
||||
// SessionManager operation labels
|
||||
const (
|
||||
SessionManager = "session"
|
||||
|
||||
OperationRegisterUdpSession = "register_udp_session"
|
||||
OperationUnregisterUdpSession = "unregister_udp_session"
|
||||
)
|
||||
|
||||
// RegistrationServer operation labels
|
||||
const (
|
||||
Registration = "registration"
|
||||
|
||||
OperationRegisterConnection = "register_connection"
|
||||
OperationUnregisterConnection = "unregister_connection"
|
||||
OperationUpdateLocalConfiguration = "update_local_configuration"
|
||||
)
|
||||
|
||||
type rpcMetrics struct {
|
||||
serverOperations *prometheus.CounterVec
|
||||
serverFailures *prometheus.CounterVec
|
||||
serverOperationsLatency *prometheus.HistogramVec
|
||||
|
||||
ClientOperations *prometheus.CounterVec
|
||||
ClientFailures *prometheus.CounterVec
|
||||
ClientOperationsLatency *prometheus.HistogramVec
|
||||
}
|
||||
|
||||
var CapnpMetrics *rpcMetrics = &rpcMetrics{
|
||||
serverOperations: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: metricsNamespace,
|
||||
Subsystem: rpcSubsystem,
|
||||
Name: "server_operations",
|
||||
Help: "Number of rpc methods by handler served",
|
||||
},
|
||||
[]string{"handler", "method"},
|
||||
),
|
||||
serverFailures: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: metricsNamespace,
|
||||
Subsystem: rpcSubsystem,
|
||||
Name: "server_failures",
|
||||
Help: "Number of rpc methods failures by handler served",
|
||||
},
|
||||
[]string{"handler", "method"},
|
||||
),
|
||||
serverOperationsLatency: prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: metricsNamespace,
|
||||
Subsystem: rpcSubsystem,
|
||||
Name: "server_latency_secs",
|
||||
Help: "Latency of rpc methods by handler served",
|
||||
// Bucket starts at 50ms, each bucket grows by a factor of 3, up to 5 buckets and is expressed as seconds:
|
||||
// 50ms, 150ms, 450ms, 1350ms, 4050ms
|
||||
Buckets: prometheus.ExponentialBuckets(0.05, 3, 5),
|
||||
},
|
||||
[]string{"handler", "method"},
|
||||
),
|
||||
ClientOperations: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: metricsNamespace,
|
||||
Subsystem: rpcSubsystem,
|
||||
Name: "client_operations",
|
||||
Help: "Number of rpc methods by handler requested",
|
||||
},
|
||||
[]string{"handler", "method"},
|
||||
),
|
||||
ClientFailures: prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: metricsNamespace,
|
||||
Subsystem: rpcSubsystem,
|
||||
Name: "client_failures",
|
||||
Help: "Number of rpc method failures by handler requested",
|
||||
},
|
||||
[]string{"handler", "method"},
|
||||
),
|
||||
ClientOperationsLatency: prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: metricsNamespace,
|
||||
Subsystem: rpcSubsystem,
|
||||
Name: "client_latency_secs",
|
||||
Help: "Latency of rpc methods by handler requested",
|
||||
// Bucket starts at 50ms, each bucket grows by a factor of 3, up to 5 buckets and is expressed as seconds:
|
||||
// 50ms, 150ms, 450ms, 1350ms, 4050ms
|
||||
Buckets: prometheus.ExponentialBuckets(0.05, 3, 5),
|
||||
},
|
||||
[]string{"handler", "method"},
|
||||
),
|
||||
}
|
||||
|
||||
func ObserveServerHandler(inner func() error, handler, method string) error {
|
||||
defer CapnpMetrics.serverOperations.WithLabelValues(handler, method).Inc()
|
||||
timer := prometheus.NewTimer(prometheus.ObserverFunc(func(s float64) {
|
||||
CapnpMetrics.serverOperationsLatency.WithLabelValues(handler, method).Observe(s)
|
||||
}))
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
err := inner()
|
||||
if err != nil {
|
||||
CapnpMetrics.serverFailures.WithLabelValues(handler, method).Inc()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func NewClientOperationLatencyObserver(server string, method string) *prometheus.Timer {
|
||||
return prometheus.NewTimer(prometheus.ObserverFunc(func(s float64) {
|
||||
CapnpMetrics.ClientOperationsLatency.WithLabelValues(server, method).Observe(s)
|
||||
}))
|
||||
}
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(CapnpMetrics.serverOperations)
|
||||
prometheus.MustRegister(CapnpMetrics.serverFailures)
|
||||
prometheus.MustRegister(CapnpMetrics.serverOperationsLatency)
|
||||
prometheus.MustRegister(CapnpMetrics.ClientOperations)
|
||||
prometheus.MustRegister(CapnpMetrics.ClientFailures)
|
||||
prometheus.MustRegister(CapnpMetrics.ClientOperationsLatency)
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package pogs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AuthenticateResponse is the serialized response from the Authenticate RPC.
|
||||
// It's a 1:1 representation of the capnp message, so it's not very useful for programmers.
|
||||
// Instead, you should call the `Outcome()` method to get a programmer-friendly sum type, with one
|
||||
// case for each possible outcome.
|
||||
type AuthenticateResponse struct {
|
||||
PermanentErr string
|
||||
RetryableErr string
|
||||
Jwt []byte
|
||||
HoursUntilRefresh uint8
|
||||
}
|
||||
|
||||
// Outcome turns the deserialized response of Authenticate into a programmer-friendly sum type.
|
||||
func (ar AuthenticateResponse) Outcome() AuthOutcome {
|
||||
// If the user's authentication was unsuccessful, the server will return an error explaining why.
|
||||
// cloudflared should fatal with this error.
|
||||
if ar.PermanentErr != "" {
|
||||
return NewAuthFail(errors.New(ar.PermanentErr))
|
||||
}
|
||||
|
||||
// If there was a network error, then cloudflared should retry later,
|
||||
// because origintunneld couldn't prove whether auth was correct or not.
|
||||
if ar.RetryableErr != "" {
|
||||
return NewAuthUnknown(errors.New(ar.RetryableErr), ar.HoursUntilRefresh)
|
||||
}
|
||||
|
||||
// If auth succeeded, return the token and refresh it when instructed.
|
||||
if len(ar.Jwt) > 0 {
|
||||
return NewAuthSuccess(ar.Jwt, ar.HoursUntilRefresh)
|
||||
}
|
||||
|
||||
// Otherwise the state got messed up.
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthOutcome is a programmer-friendly sum type denoting the possible outcomes of Authenticate.
|
||||
type AuthOutcome interface {
|
||||
isAuthOutcome()
|
||||
// Serialize into an AuthenticateResponse which can be sent via Capnp
|
||||
Serialize() AuthenticateResponse
|
||||
}
|
||||
|
||||
// AuthSuccess means the backend successfully authenticated this cloudflared.
|
||||
type AuthSuccess struct {
|
||||
jwt []byte
|
||||
hoursUntilRefresh uint8
|
||||
}
|
||||
|
||||
func NewAuthSuccess(jwt []byte, hoursUntilRefresh uint8) AuthSuccess {
|
||||
return AuthSuccess{jwt: jwt, hoursUntilRefresh: hoursUntilRefresh}
|
||||
}
|
||||
|
||||
func (ao AuthSuccess) JWT() []byte {
|
||||
return ao.jwt
|
||||
}
|
||||
|
||||
// RefreshAfter is how long cloudflared should wait before rerunning Authenticate.
|
||||
func (ao AuthSuccess) RefreshAfter() time.Duration {
|
||||
return hoursToTime(ao.hoursUntilRefresh)
|
||||
}
|
||||
|
||||
// Serialize into an AuthenticateResponse which can be sent via Capnp
|
||||
func (ao AuthSuccess) Serialize() AuthenticateResponse {
|
||||
return AuthenticateResponse{
|
||||
Jwt: ao.jwt,
|
||||
HoursUntilRefresh: ao.hoursUntilRefresh,
|
||||
}
|
||||
}
|
||||
|
||||
func (ao AuthSuccess) isAuthOutcome() {}
|
||||
|
||||
// AuthFail means this cloudflared has the wrong auth and should exit.
|
||||
type AuthFail struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func NewAuthFail(err error) AuthFail {
|
||||
return AuthFail{err: err}
|
||||
}
|
||||
|
||||
func (ao AuthFail) Error() string {
|
||||
return ao.err.Error()
|
||||
}
|
||||
|
||||
// Serialize into an AuthenticateResponse which can be sent via Capnp
|
||||
func (ao AuthFail) Serialize() AuthenticateResponse {
|
||||
return AuthenticateResponse{
|
||||
PermanentErr: ao.err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func (ao AuthFail) isAuthOutcome() {}
|
||||
|
||||
// AuthUnknown means the backend couldn't finish checking authentication. Try again later.
|
||||
type AuthUnknown struct {
|
||||
err error
|
||||
hoursUntilRefresh uint8
|
||||
}
|
||||
|
||||
func NewAuthUnknown(err error, hoursUntilRefresh uint8) AuthUnknown {
|
||||
return AuthUnknown{err: err, hoursUntilRefresh: hoursUntilRefresh}
|
||||
}
|
||||
|
||||
func (ao AuthUnknown) Error() string {
|
||||
return ao.err.Error()
|
||||
}
|
||||
|
||||
// RefreshAfter is how long cloudflared should wait before rerunning Authenticate.
|
||||
func (ao AuthUnknown) RefreshAfter() time.Duration {
|
||||
return hoursToTime(ao.hoursUntilRefresh)
|
||||
}
|
||||
|
||||
// Serialize into an AuthenticateResponse which can be sent via Capnp
|
||||
func (ao AuthUnknown) Serialize() AuthenticateResponse {
|
||||
return AuthenticateResponse{
|
||||
RetryableErr: ao.err.Error(),
|
||||
HoursUntilRefresh: ao.hoursUntilRefresh,
|
||||
}
|
||||
}
|
||||
|
||||
func (ao AuthUnknown) isAuthOutcome() {}
|
||||
|
||||
func hoursToTime(hours uint8) time.Duration {
|
||||
return time.Duration(hours) * time.Hour
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package pogs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"zombiezen.com/go/capnproto2/pogs"
|
||||
"zombiezen.com/go/capnproto2/server"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
)
|
||||
|
||||
func (i TunnelServer_PogsImpl) Authenticate(p tunnelrpc.TunnelServer_authenticate) error {
|
||||
originCert, err := p.Params.OriginCert()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostname, err := p.Params.Hostname()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options, err := p.Params.Options()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pogsOptions, err := UnmarshalRegistrationOptions(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server.Ack(p.Options)
|
||||
resp, err := i.impl.Authenticate(p.Ctx, originCert, hostname, pogsOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := p.Results.NewResult()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return MarshalAuthenticateResponse(result, resp)
|
||||
}
|
||||
|
||||
func MarshalAuthenticateResponse(s tunnelrpc.AuthenticateResponse, p *AuthenticateResponse) error {
|
||||
return pogs.Insert(tunnelrpc.AuthenticateResponse_TypeID, s.Struct, p)
|
||||
}
|
||||
|
||||
func (c TunnelServer_PogsClient) Authenticate(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) (*AuthenticateResponse, error) {
|
||||
client := tunnelrpc.TunnelServer{Client: c.Client}
|
||||
promise := client.Authenticate(ctx, func(p tunnelrpc.TunnelServer_authenticate_Params) error {
|
||||
err := p.SetOriginCert(originCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.SetHostname(hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registrationOptions, err := p.NewOptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = MarshalRegistrationOptions(registrationOptions, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
retval, err := promise.Result().Struct()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return UnmarshalAuthenticateResponse(retval)
|
||||
}
|
||||
|
||||
func UnmarshalAuthenticateResponse(s tunnelrpc.AuthenticateResponse) (*AuthenticateResponse, error) {
|
||||
p := new(AuthenticateResponse)
|
||||
err := pogs.Extract(p, tunnelrpc.AuthenticateResponse_TypeID, s.Struct)
|
||||
return p, err
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package pogs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
)
|
||||
|
||||
// Ensure the AuthOutcome sum is correct
|
||||
var _ AuthOutcome = &AuthSuccess{}
|
||||
var _ AuthOutcome = &AuthFail{}
|
||||
var _ AuthOutcome = &AuthUnknown{}
|
||||
|
||||
// Unit tests for AuthenticateResponse.Outcome()
|
||||
func TestAuthenticateResponseOutcome(t *testing.T) {
|
||||
type fields struct {
|
||||
PermanentErr string
|
||||
RetryableErr string
|
||||
Jwt []byte
|
||||
HoursUntilRefresh uint8
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want AuthOutcome
|
||||
}{
|
||||
{"success",
|
||||
fields{Jwt: []byte("asdf"), HoursUntilRefresh: 6},
|
||||
AuthSuccess{jwt: []byte("asdf"), hoursUntilRefresh: 6},
|
||||
},
|
||||
{"fail",
|
||||
fields{PermanentErr: "bad creds"},
|
||||
AuthFail{err: fmt.Errorf("bad creds")},
|
||||
},
|
||||
{"error",
|
||||
fields{RetryableErr: "bad conn", HoursUntilRefresh: 6},
|
||||
AuthUnknown{err: fmt.Errorf("bad conn"), hoursUntilRefresh: 6},
|
||||
},
|
||||
{"nil (no fields are set)",
|
||||
fields{},
|
||||
nil,
|
||||
},
|
||||
{"nil (too few fields are set)",
|
||||
fields{HoursUntilRefresh: 6},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ar := AuthenticateResponse{
|
||||
PermanentErr: tt.fields.PermanentErr,
|
||||
RetryableErr: tt.fields.RetryableErr,
|
||||
Jwt: tt.fields.Jwt,
|
||||
HoursUntilRefresh: tt.fields.HoursUntilRefresh,
|
||||
}
|
||||
got := ar.Outcome()
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("AuthenticateResponse.Outcome() = %T, want %v", got, tt.want)
|
||||
}
|
||||
if got != nil && !reflect.DeepEqual(got.Serialize(), ar) {
|
||||
t.Errorf(".Outcome() and .Serialize() should be inverses but weren't. Expected %v, got %v", ar, got.Serialize())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthSuccess(t *testing.T) {
|
||||
input := NewAuthSuccess([]byte("asdf"), 6)
|
||||
output, ok := input.Serialize().Outcome().(AuthSuccess)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, input, output)
|
||||
}
|
||||
|
||||
func TestAuthUnknown(t *testing.T) {
|
||||
input := NewAuthUnknown(fmt.Errorf("pdx unreachable"), 6)
|
||||
output, ok := input.Serialize().Outcome().(AuthUnknown)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, input, output)
|
||||
}
|
||||
|
||||
func TestAuthFail(t *testing.T) {
|
||||
input := NewAuthFail(fmt.Errorf("wrong creds"))
|
||||
output, ok := input.Serialize().Outcome().(AuthFail)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, input, output)
|
||||
}
|
||||
|
||||
func TestWhenToRefresh(t *testing.T) {
|
||||
expected := 4 * time.Hour
|
||||
actual := hoursToTime(4)
|
||||
if expected != actual {
|
||||
t.Fatalf("expected %v hours, got %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that serializing and deserializing AuthenticationResponse undo each other.
|
||||
func TestSerializeAuthenticationResponse(t *testing.T) {
|
||||
|
||||
tests := []*AuthenticateResponse{
|
||||
{
|
||||
Jwt: []byte("\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98"),
|
||||
HoursUntilRefresh: 24,
|
||||
},
|
||||
{
|
||||
PermanentErr: "bad auth",
|
||||
},
|
||||
{
|
||||
RetryableErr: "bad connection",
|
||||
HoursUntilRefresh: 24,
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range tests {
|
||||
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
|
||||
assert.NoError(t, err)
|
||||
capnpEntity, err := tunnelrpc.NewAuthenticateResponse(seg)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fatal("Couldn't initialize a new message")
|
||||
}
|
||||
err = MarshalAuthenticateResponse(capnpEntity, testCase)
|
||||
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
|
||||
continue
|
||||
}
|
||||
result, err := UnmarshalAuthenticateResponse(capnpEntity)
|
||||
if !assert.NoError(t, err, "testCase index %v failed to unmarshal", i) {
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, testCase, result, "testCase index %v didn't preserve struct through marshalling and unmarshalling", i)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package pogs
|
||||
|
||||
import (
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/proto"
|
||||
)
|
||||
|
||||
type CloudflaredServer interface {
|
||||
@@ -16,8 +17,8 @@ type CloudflaredServer_PogsImpl struct {
|
||||
ConfigurationManager_PogsImpl
|
||||
}
|
||||
|
||||
func CloudflaredServer_ServerToClient(s SessionManager, c ConfigurationManager) tunnelrpc.CloudflaredServer {
|
||||
return tunnelrpc.CloudflaredServer_ServerToClient(CloudflaredServer_PogsImpl{
|
||||
func CloudflaredServer_ServerToClient(s SessionManager, c ConfigurationManager) proto.CloudflaredServer {
|
||||
return proto.CloudflaredServer_ServerToClient(CloudflaredServer_PogsImpl{
|
||||
SessionManager_PogsImpl: SessionManager_PogsImpl{s},
|
||||
ConfigurationManager_PogsImpl: ConfigurationManager_PogsImpl{c},
|
||||
})
|
||||
@@ -4,13 +4,16 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
"zombiezen.com/go/capnproto2/server"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/metrics"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/proto"
|
||||
)
|
||||
|
||||
type ConfigurationManager interface {
|
||||
// UpdateConfiguration is the call provided to cloudflared to load the latest remote configuration.
|
||||
UpdateConfiguration(ctx context.Context, version int32, config []byte) *UpdateConfigurationResponse
|
||||
}
|
||||
|
||||
@@ -18,11 +21,15 @@ type ConfigurationManager_PogsImpl struct {
|
||||
impl ConfigurationManager
|
||||
}
|
||||
|
||||
func ConfigurationManager_ServerToClient(c ConfigurationManager) tunnelrpc.ConfigurationManager {
|
||||
return tunnelrpc.ConfigurationManager_ServerToClient(ConfigurationManager_PogsImpl{c})
|
||||
func ConfigurationManager_ServerToClient(c ConfigurationManager) proto.ConfigurationManager {
|
||||
return proto.ConfigurationManager_ServerToClient(ConfigurationManager_PogsImpl{c})
|
||||
}
|
||||
|
||||
func (i ConfigurationManager_PogsImpl) UpdateConfiguration(p tunnelrpc.ConfigurationManager_updateConfiguration) error {
|
||||
func (i ConfigurationManager_PogsImpl) UpdateConfiguration(p proto.ConfigurationManager_updateConfiguration) error {
|
||||
return metrics.ObserveServerHandler(func() error { return i.updateConfiguration(p) }, metrics.ConfigurationManager, metrics.OperationUpdateConfiguration)
|
||||
}
|
||||
|
||||
func (i ConfigurationManager_PogsImpl) updateConfiguration(p proto.ConfigurationManager_updateConfiguration) error {
|
||||
server.Ack(p.Options)
|
||||
|
||||
version := p.Params.Version()
|
||||
@@ -51,8 +58,8 @@ func (c ConfigurationManager_PogsClient) Close() error {
|
||||
}
|
||||
|
||||
func (c ConfigurationManager_PogsClient) UpdateConfiguration(ctx context.Context, version int32, config []byte) (*UpdateConfigurationResponse, error) {
|
||||
client := tunnelrpc.ConfigurationManager{Client: c.Client}
|
||||
promise := client.UpdateConfiguration(ctx, func(p tunnelrpc.ConfigurationManager_updateConfiguration_Params) error {
|
||||
client := proto.ConfigurationManager{Client: c.Client}
|
||||
promise := client.UpdateConfiguration(ctx, func(p proto.ConfigurationManager_updateConfiguration_Params) error {
|
||||
p.SetVersion(version)
|
||||
return p.SetConfig(config)
|
||||
})
|
||||
@@ -74,7 +81,7 @@ type UpdateConfigurationResponse struct {
|
||||
Err error `json:"err"`
|
||||
}
|
||||
|
||||
func (p *UpdateConfigurationResponse) Marshal(s tunnelrpc.UpdateConfigurationResponse) error {
|
||||
func (p *UpdateConfigurationResponse) Marshal(s proto.UpdateConfigurationResponse) error {
|
||||
s.SetLatestAppliedVersion(p.LastAppliedVersion)
|
||||
if p.Err != nil {
|
||||
return s.SetErr(p.Err.Error())
|
||||
@@ -82,7 +89,7 @@ func (p *UpdateConfigurationResponse) Marshal(s tunnelrpc.UpdateConfigurationRes
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *UpdateConfigurationResponse) Unmarshal(s tunnelrpc.UpdateConfigurationResponse) error {
|
||||
func (p *UpdateConfigurationResponse) Unmarshal(s proto.UpdateConfigurationResponse) error {
|
||||
p.LastAppliedVersion = s.LatestAppliedVersion()
|
||||
respErr, err := s.Err()
|
||||
if err != nil {
|
||||
@@ -1,4 +1,4 @@
|
||||
package quic
|
||||
package pogs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
"zombiezen.com/go/capnproto2/pogs"
|
||||
|
||||
"github.com/cloudflare/cloudflared/quic/schema"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/proto"
|
||||
)
|
||||
|
||||
// ConnectionType indicates the type of underlying connection proxied within the QUIC stream.
|
||||
@@ -52,26 +52,26 @@ func (r *ConnectRequest) MetadataMap() map[string]string {
|
||||
return metadataMap
|
||||
}
|
||||
|
||||
func (r *ConnectRequest) fromPogs(msg *capnp.Message) error {
|
||||
metadata, err := schema.ReadRootConnectRequest(msg)
|
||||
func (r *ConnectRequest) FromPogs(msg *capnp.Message) error {
|
||||
metadata, err := proto.ReadRootConnectRequest(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pogs.Extract(r, schema.ConnectRequest_TypeID, metadata.Struct)
|
||||
return pogs.Extract(r, proto.ConnectRequest_TypeID, metadata.Struct)
|
||||
}
|
||||
|
||||
func (r *ConnectRequest) toPogs() (*capnp.Message, error) {
|
||||
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)
|
||||
root, err := proto.NewRootConnectRequest(seg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := pogs.Insert(schema.ConnectRequest_TypeID, root.Struct, r); err != nil {
|
||||
if err := pogs.Insert(proto.ConnectRequest_TypeID, root.Struct, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -84,26 +84,26 @@ type ConnectResponse struct {
|
||||
Metadata []Metadata `capnp:"metadata"`
|
||||
}
|
||||
|
||||
func (r *ConnectResponse) fromPogs(msg *capnp.Message) error {
|
||||
metadata, err := schema.ReadRootConnectResponse(msg)
|
||||
func (r *ConnectResponse) FromPogs(msg *capnp.Message) error {
|
||||
metadata, err := proto.ReadRootConnectResponse(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pogs.Extract(r, schema.ConnectResponse_TypeID, metadata.Struct)
|
||||
return pogs.Extract(r, proto.ConnectResponse_TypeID, metadata.Struct)
|
||||
}
|
||||
|
||||
func (r *ConnectResponse) toPogs() (*capnp.Message, error) {
|
||||
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)
|
||||
root, err := proto.NewRootConnectResponse(seg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := pogs.Insert(schema.ConnectResponse_TypeID, root.Struct, r); err != nil {
|
||||
if err := pogs.Insert(proto.ConnectResponse_TypeID, root.Struct, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package pogs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"zombiezen.com/go/capnproto2/server"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
)
|
||||
|
||||
func (i TunnelServer_PogsImpl) ReconnectTunnel(p tunnelrpc.TunnelServer_reconnectTunnel) error {
|
||||
jwt, err := p.Params.Jwt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
eventDigest, err := p.Params.EventDigest()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
connDigest, err := p.Params.ConnDigest()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostname, err := p.Params.Hostname()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options, err := p.Params.Options()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pogsOptions, err := UnmarshalRegistrationOptions(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
server.Ack(p.Options)
|
||||
registration, err := i.impl.ReconnectTunnel(p.Ctx, jwt, eventDigest, connDigest, hostname, pogsOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := p.Results.NewResult()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return MarshalTunnelRegistration(result, registration)
|
||||
}
|
||||
|
||||
func (c TunnelServer_PogsClient) ReconnectTunnel(
|
||||
ctx context.Context,
|
||||
jwt,
|
||||
eventDigest []byte,
|
||||
connDigest []byte,
|
||||
hostname string,
|
||||
options *RegistrationOptions,
|
||||
) *TunnelRegistration {
|
||||
client := tunnelrpc.TunnelServer{Client: c.Client}
|
||||
promise := client.ReconnectTunnel(ctx, func(p tunnelrpc.TunnelServer_reconnectTunnel_Params) error {
|
||||
err := p.SetJwt(jwt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.SetEventDigest(eventDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.SetConnDigest(connDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.SetHostname(hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registrationOptions, err := p.NewOptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = MarshalRegistrationOptions(registrationOptions, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
retval, err := promise.Result().Struct()
|
||||
if err != nil {
|
||||
return NewRetryableRegistrationError(err, defaultRetryAfterSeconds).Serialize()
|
||||
}
|
||||
registration, err := UnmarshalTunnelRegistration(retval)
|
||||
if err != nil {
|
||||
return NewRetryableRegistrationError(err, defaultRetryAfterSeconds).Serialize()
|
||||
}
|
||||
return registration
|
||||
}
|
||||
@@ -12,12 +12,18 @@ import (
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
"zombiezen.com/go/capnproto2/server"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/metrics"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/proto"
|
||||
)
|
||||
|
||||
type RegistrationServer interface {
|
||||
// RegisterConnection is the call typically handled by the edge to initiate and authenticate a new connection
|
||||
// for cloudflared.
|
||||
RegisterConnection(ctx context.Context, auth TunnelAuth, tunnelID uuid.UUID, connIndex byte, options *ConnectionOptions) (*ConnectionDetails, error)
|
||||
// UnregisterConnection is the call typically handled by the edge to close an existing connection for cloudflared.
|
||||
UnregisterConnection(ctx context.Context)
|
||||
// UpdateLocalConfiguration is the call typically handled by the edge for cloudflared to provide the current
|
||||
// configuration it is operating with.
|
||||
UpdateLocalConfiguration(ctx context.Context, config []byte) error
|
||||
}
|
||||
|
||||
@@ -25,11 +31,15 @@ type RegistrationServer_PogsImpl struct {
|
||||
impl RegistrationServer
|
||||
}
|
||||
|
||||
func RegistrationServer_ServerToClient(s RegistrationServer) tunnelrpc.RegistrationServer {
|
||||
return tunnelrpc.RegistrationServer_ServerToClient(RegistrationServer_PogsImpl{s})
|
||||
func RegistrationServer_ServerToClient(s RegistrationServer) proto.RegistrationServer {
|
||||
return proto.RegistrationServer_ServerToClient(RegistrationServer_PogsImpl{s})
|
||||
}
|
||||
|
||||
func (i RegistrationServer_PogsImpl) RegisterConnection(p tunnelrpc.RegistrationServer_registerConnection) error {
|
||||
func (i RegistrationServer_PogsImpl) RegisterConnection(p proto.RegistrationServer_registerConnection) error {
|
||||
return metrics.ObserveServerHandler(func() error { return i.registerConnection(p) }, metrics.Registration, metrics.OperationRegisterConnection)
|
||||
}
|
||||
|
||||
func (i RegistrationServer_PogsImpl) registerConnection(p proto.RegistrationServer_registerConnection) error {
|
||||
server.Ack(p.Options)
|
||||
|
||||
auth, err := p.Params.Auth()
|
||||
@@ -82,14 +92,19 @@ func (i RegistrationServer_PogsImpl) RegisterConnection(p tunnelrpc.Registration
|
||||
}
|
||||
}
|
||||
|
||||
func (i RegistrationServer_PogsImpl) UnregisterConnection(p tunnelrpc.RegistrationServer_unregisterConnection) error {
|
||||
server.Ack(p.Options)
|
||||
|
||||
i.impl.UnregisterConnection(p.Ctx)
|
||||
return nil
|
||||
func (i RegistrationServer_PogsImpl) UnregisterConnection(p proto.RegistrationServer_unregisterConnection) error {
|
||||
return metrics.ObserveServerHandler(func() error {
|
||||
server.Ack(p.Options)
|
||||
i.impl.UnregisterConnection(p.Ctx)
|
||||
return nil // No metrics will be reported for failure as this method has no return value
|
||||
}, metrics.Registration, metrics.OperationUnregisterConnection)
|
||||
}
|
||||
|
||||
func (i RegistrationServer_PogsImpl) UpdateLocalConfiguration(c tunnelrpc.RegistrationServer_updateLocalConfiguration) error {
|
||||
func (i RegistrationServer_PogsImpl) UpdateLocalConfiguration(p proto.RegistrationServer_updateLocalConfiguration) error {
|
||||
return metrics.ObserveServerHandler(func() error { return i.updateLocalConfiguration(p) }, metrics.Registration, metrics.OperationUpdateLocalConfiguration)
|
||||
}
|
||||
|
||||
func (i RegistrationServer_PogsImpl) updateLocalConfiguration(c proto.RegistrationServer_updateLocalConfiguration) error {
|
||||
server.Ack(c.Options)
|
||||
|
||||
configBytes, err := c.Params.Config()
|
||||
@@ -105,14 +120,21 @@ type RegistrationServer_PogsClient struct {
|
||||
Conn *rpc.Conn
|
||||
}
|
||||
|
||||
func NewRegistrationServer_PogsClient(client capnp.Client, conn *rpc.Conn) RegistrationServer_PogsClient {
|
||||
return RegistrationServer_PogsClient{
|
||||
Client: client,
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
func (c RegistrationServer_PogsClient) Close() error {
|
||||
c.Client.Close()
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c RegistrationServer_PogsClient) RegisterConnection(ctx context.Context, auth TunnelAuth, tunnelID uuid.UUID, connIndex byte, options *ConnectionOptions) (*ConnectionDetails, error) {
|
||||
client := tunnelrpc.TunnelServer{Client: c.Client}
|
||||
promise := client.RegisterConnection(ctx, func(p tunnelrpc.RegistrationServer_registerConnection_Params) error {
|
||||
client := proto.TunnelServer{Client: c.Client}
|
||||
promise := client.RegisterConnection(ctx, func(p proto.RegistrationServer_registerConnection_Params) error {
|
||||
tunnelAuth, err := p.NewAuth()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -145,7 +167,7 @@ func (c RegistrationServer_PogsClient) RegisterConnection(ctx context.Context, a
|
||||
}
|
||||
result := response.Result()
|
||||
switch result.Which() {
|
||||
case tunnelrpc.ConnectionResponse_result_Which_error:
|
||||
case proto.ConnectionResponse_result_Which_error:
|
||||
resultError, err := result.Error()
|
||||
if err != nil {
|
||||
return nil, wrapRPCError(err)
|
||||
@@ -160,7 +182,7 @@ func (c RegistrationServer_PogsClient) RegisterConnection(ctx context.Context, a
|
||||
}
|
||||
return nil, err
|
||||
|
||||
case tunnelrpc.ConnectionResponse_result_Which_connectionDetails:
|
||||
case proto.ConnectionResponse_result_Which_connectionDetails:
|
||||
connDetails, err := result.ConnectionDetails()
|
||||
if err != nil {
|
||||
return nil, wrapRPCError(err)
|
||||
@@ -176,8 +198,8 @@ func (c RegistrationServer_PogsClient) RegisterConnection(ctx context.Context, a
|
||||
}
|
||||
|
||||
func (c RegistrationServer_PogsClient) SendLocalConfiguration(ctx context.Context, config []byte) error {
|
||||
client := tunnelrpc.TunnelServer{Client: c.Client}
|
||||
promise := client.UpdateLocalConfiguration(ctx, func(p tunnelrpc.RegistrationServer_updateLocalConfiguration_Params) error {
|
||||
client := proto.TunnelServer{Client: c.Client}
|
||||
promise := client.UpdateLocalConfiguration(ctx, func(p proto.RegistrationServer_updateLocalConfiguration_Params) error {
|
||||
if err := p.SetConfig(config); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -194,8 +216,8 @@ func (c RegistrationServer_PogsClient) SendLocalConfiguration(ctx context.Contex
|
||||
}
|
||||
|
||||
func (c RegistrationServer_PogsClient) UnregisterConnection(ctx context.Context) error {
|
||||
client := tunnelrpc.TunnelServer{Client: c.Client}
|
||||
promise := client.UnregisterConnection(ctx, func(p tunnelrpc.RegistrationServer_unregisterConnection_Params) error {
|
||||
client := proto.TunnelServer{Client: c.Client}
|
||||
promise := client.UnregisterConnection(ctx, func(p proto.RegistrationServer_unregisterConnection_Params) error {
|
||||
return nil
|
||||
})
|
||||
_, err := promise.Struct()
|
||||
@@ -225,20 +247,20 @@ type TunnelAuth struct {
|
||||
TunnelSecret []byte
|
||||
}
|
||||
|
||||
func (p *ConnectionOptions) MarshalCapnproto(s tunnelrpc.ConnectionOptions) error {
|
||||
return pogs.Insert(tunnelrpc.ConnectionOptions_TypeID, s.Struct, p)
|
||||
func (p *ConnectionOptions) MarshalCapnproto(s proto.ConnectionOptions) error {
|
||||
return pogs.Insert(proto.ConnectionOptions_TypeID, s.Struct, p)
|
||||
}
|
||||
|
||||
func (p *ConnectionOptions) UnmarshalCapnproto(s tunnelrpc.ConnectionOptions) error {
|
||||
return pogs.Extract(p, tunnelrpc.ConnectionOptions_TypeID, s.Struct)
|
||||
func (p *ConnectionOptions) UnmarshalCapnproto(s proto.ConnectionOptions) error {
|
||||
return pogs.Extract(p, proto.ConnectionOptions_TypeID, s.Struct)
|
||||
}
|
||||
|
||||
func (a *TunnelAuth) MarshalCapnproto(s tunnelrpc.TunnelAuth) error {
|
||||
return pogs.Insert(tunnelrpc.TunnelAuth_TypeID, s.Struct, a)
|
||||
func (a *TunnelAuth) MarshalCapnproto(s proto.TunnelAuth) error {
|
||||
return pogs.Insert(proto.TunnelAuth_TypeID, s.Struct, a)
|
||||
}
|
||||
|
||||
func (a *TunnelAuth) UnmarshalCapnproto(s tunnelrpc.TunnelAuth) error {
|
||||
return pogs.Extract(a, tunnelrpc.TunnelAuth_TypeID, s.Struct)
|
||||
func (a *TunnelAuth) UnmarshalCapnproto(s proto.TunnelAuth) error {
|
||||
return pogs.Extract(a, proto.TunnelAuth_TypeID, s.Struct)
|
||||
}
|
||||
|
||||
type ConnectionDetails struct {
|
||||
@@ -247,7 +269,7 @@ type ConnectionDetails struct {
|
||||
TunnelIsRemotelyManaged bool
|
||||
}
|
||||
|
||||
func (details *ConnectionDetails) MarshalCapnproto(s tunnelrpc.ConnectionDetails) error {
|
||||
func (details *ConnectionDetails) MarshalCapnproto(s proto.ConnectionDetails) error {
|
||||
if err := s.SetUuid(details.UUID[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -259,7 +281,7 @@ func (details *ConnectionDetails) MarshalCapnproto(s tunnelrpc.ConnectionDetails
|
||||
return nil
|
||||
}
|
||||
|
||||
func (details *ConnectionDetails) UnmarshalCapnproto(s tunnelrpc.ConnectionDetails) error {
|
||||
func (details *ConnectionDetails) UnmarshalCapnproto(s proto.ConnectionDetails) error {
|
||||
uuidBytes, err := s.Uuid()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -277,7 +299,7 @@ func (details *ConnectionDetails) UnmarshalCapnproto(s tunnelrpc.ConnectionDetai
|
||||
return err
|
||||
}
|
||||
|
||||
func MarshalError(s tunnelrpc.ConnectionError, err error) error {
|
||||
func MarshalError(s proto.ConnectionError, err error) error {
|
||||
if err := s.SetCause(err.Error()); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/proto"
|
||||
)
|
||||
|
||||
const testAccountTag = "abc123"
|
||||
@@ -34,7 +34,7 @@ func TestMarshalConnectionOptions(t *testing.T) {
|
||||
|
||||
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
|
||||
require.NoError(t, err)
|
||||
capnpOpts, err := tunnelrpc.NewConnectionOptions(seg)
|
||||
capnpOpts, err := proto.NewConnectionOptions(seg)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = orig.MarshalCapnproto(capnpOpts)
|
||||
@@ -54,18 +54,14 @@ func TestConnectionRegistrationRPC(t *testing.T) {
|
||||
|
||||
// Server-side
|
||||
testImpl := testConnectionRegistrationServer{}
|
||||
srv := TunnelServer_ServerToClient(&testImpl)
|
||||
srv := RegistrationServer_ServerToClient(&testImpl)
|
||||
serverConn := rpc.NewConn(t1, rpc.MainInterface(srv.Client))
|
||||
defer serverConn.Wait()
|
||||
|
||||
ctx := context.Background()
|
||||
clientConn := rpc.NewConn(t2)
|
||||
defer clientConn.Close()
|
||||
client := TunnelServer_PogsClient{
|
||||
RegistrationServer_PogsClient: RegistrationServer_PogsClient{
|
||||
Client: clientConn.Bootstrap(ctx),
|
||||
Conn: clientConn,
|
||||
},
|
||||
client := RegistrationServer_PogsClient{
|
||||
Client: clientConn.Bootstrap(ctx),
|
||||
Conn: clientConn,
|
||||
}
|
||||
@@ -123,8 +119,6 @@ func TestConnectionRegistrationRPC(t *testing.T) {
|
||||
}
|
||||
|
||||
type testConnectionRegistrationServer struct {
|
||||
mockTunnelServerBase
|
||||
|
||||
details *ConnectionDetails
|
||||
err error
|
||||
}
|
||||
@@ -147,3 +141,7 @@ func (t *testConnectionRegistrationServer) RegisterConnection(ctx context.Contex
|
||||
|
||||
panic("either details or err mush be set")
|
||||
}
|
||||
|
||||
func (t *testConnectionRegistrationServer) UnregisterConnection(ctx context.Context) {
|
||||
panic("unimplemented: UnregisterConnection")
|
||||
}
|
||||
@@ -6,11 +6,13 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
"github.com/google/uuid"
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
"zombiezen.com/go/capnproto2/server"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/metrics"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/proto"
|
||||
)
|
||||
|
||||
type SessionManager interface {
|
||||
@@ -26,11 +28,15 @@ type SessionManager_PogsImpl struct {
|
||||
impl SessionManager
|
||||
}
|
||||
|
||||
func SessionManager_ServerToClient(s SessionManager) tunnelrpc.SessionManager {
|
||||
return tunnelrpc.SessionManager_ServerToClient(SessionManager_PogsImpl{s})
|
||||
func SessionManager_ServerToClient(s SessionManager) proto.SessionManager {
|
||||
return proto.SessionManager_ServerToClient(SessionManager_PogsImpl{s})
|
||||
}
|
||||
|
||||
func (i SessionManager_PogsImpl) RegisterUdpSession(p tunnelrpc.SessionManager_registerUdpSession) error {
|
||||
func (i SessionManager_PogsImpl) RegisterUdpSession(p proto.SessionManager_registerUdpSession) error {
|
||||
return metrics.ObserveServerHandler(func() error { return i.registerUdpSession(p) }, metrics.SessionManager, metrics.OperationRegisterUdpSession)
|
||||
}
|
||||
|
||||
func (i SessionManager_PogsImpl) registerUdpSession(p proto.SessionManager_registerUdpSession) error {
|
||||
server.Ack(p.Options)
|
||||
|
||||
sessionIDRaw, err := p.Params.SessionId()
|
||||
@@ -76,7 +82,11 @@ func (i SessionManager_PogsImpl) RegisterUdpSession(p tunnelrpc.SessionManager_r
|
||||
return resp.Marshal(result)
|
||||
}
|
||||
|
||||
func (i SessionManager_PogsImpl) UnregisterUdpSession(p tunnelrpc.SessionManager_unregisterUdpSession) error {
|
||||
func (i SessionManager_PogsImpl) UnregisterUdpSession(p proto.SessionManager_unregisterUdpSession) error {
|
||||
return metrics.ObserveServerHandler(func() error { return i.unregisterUdpSession(p) }, metrics.SessionManager, metrics.OperationUnregisterUdpSession)
|
||||
}
|
||||
|
||||
func (i SessionManager_PogsImpl) unregisterUdpSession(p proto.SessionManager_unregisterUdpSession) error {
|
||||
server.Ack(p.Options)
|
||||
|
||||
sessionIDRaw, err := p.Params.SessionId()
|
||||
@@ -101,7 +111,7 @@ type RegisterUdpSessionResponse struct {
|
||||
Spans []byte // Spans in protobuf format
|
||||
}
|
||||
|
||||
func (p *RegisterUdpSessionResponse) Marshal(s tunnelrpc.RegisterUdpSessionResponse) error {
|
||||
func (p *RegisterUdpSessionResponse) Marshal(s proto.RegisterUdpSessionResponse) error {
|
||||
if p.Err != nil {
|
||||
return s.SetErr(p.Err.Error())
|
||||
}
|
||||
@@ -111,7 +121,7 @@ func (p *RegisterUdpSessionResponse) Marshal(s tunnelrpc.RegisterUdpSessionRespo
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *RegisterUdpSessionResponse) Unmarshal(s tunnelrpc.RegisterUdpSessionResponse) error {
|
||||
func (p *RegisterUdpSessionResponse) Unmarshal(s proto.RegisterUdpSessionResponse) error {
|
||||
respErr, err := s.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -131,14 +141,21 @@ type SessionManager_PogsClient struct {
|
||||
Conn *rpc.Conn
|
||||
}
|
||||
|
||||
func NewSessionManager_PogsClient(client capnp.Client, conn *rpc.Conn) SessionManager_PogsClient {
|
||||
return SessionManager_PogsClient{
|
||||
Client: client,
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
func (c SessionManager_PogsClient) Close() error {
|
||||
c.Client.Close()
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c SessionManager_PogsClient) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration, traceContext string) (*RegisterUdpSessionResponse, error) {
|
||||
client := tunnelrpc.SessionManager{Client: c.Client}
|
||||
promise := client.RegisterUdpSession(ctx, func(p tunnelrpc.SessionManager_registerUdpSession_Params) error {
|
||||
client := proto.SessionManager{Client: c.Client}
|
||||
promise := client.RegisterUdpSession(ctx, func(p proto.SessionManager_registerUdpSession_Params) error {
|
||||
if err := p.SetSessionId(sessionID[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -164,8 +181,8 @@ func (c SessionManager_PogsClient) RegisterUdpSession(ctx context.Context, sessi
|
||||
}
|
||||
|
||||
func (c SessionManager_PogsClient) UnregisterUdpSession(ctx context.Context, sessionID uuid.UUID, message string) error {
|
||||
client := tunnelrpc.SessionManager{Client: c.Client}
|
||||
promise := client.UnregisterUdpSession(ctx, func(p tunnelrpc.SessionManager_unregisterUdpSession_Params) error {
|
||||
client := proto.SessionManager{Client: c.Client}
|
||||
promise := client.UnregisterUdpSession(ctx, func(p proto.SessionManager_unregisterUdpSession_Params) error {
|
||||
if err := p.SetSessionId(sessionID[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package pogs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// mockTunnelServerBase provides a placeholder implementation
|
||||
// for TunnelServer interface that can be used to build
|
||||
// mocks for specific unit tests without having to implement every method
|
||||
type mockTunnelServerBase struct{}
|
||||
|
||||
func (mockTunnelServerBase) RegisterConnection(ctx context.Context, auth TunnelAuth, tunnelID uuid.UUID, connIndex byte, options *ConnectionOptions) (*ConnectionDetails, error) {
|
||||
panic("unexpected call to RegisterConnection")
|
||||
}
|
||||
|
||||
func (mockTunnelServerBase) UnregisterConnection(ctx context.Context) {
|
||||
panic("unexpected call to UnregisterConnection")
|
||||
}
|
||||
|
||||
func (mockTunnelServerBase) RegisterTunnel(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) *TunnelRegistration {
|
||||
panic("unexpected call to RegisterTunnel")
|
||||
}
|
||||
|
||||
func (mockTunnelServerBase) GetServerInfo(ctx context.Context) (*ServerInfo, error) {
|
||||
panic("unexpected call to GetServerInfo")
|
||||
}
|
||||
|
||||
func (mockTunnelServerBase) UnregisterTunnel(ctx context.Context, gracePeriodNanoSec int64) error {
|
||||
panic("unexpected call to UnregisterTunnel")
|
||||
}
|
||||
|
||||
func (mockTunnelServerBase) Authenticate(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) (*AuthenticateResponse, error) {
|
||||
panic("unexpected call to Authenticate")
|
||||
}
|
||||
|
||||
func (mockTunnelServerBase) ReconnectTunnel(ctx context.Context, jwt, eventDigest, connDigest []byte, hostname string, options *RegistrationOptions) (*TunnelRegistration, error) {
|
||||
panic("unexpected call to ReconnectTunnel")
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package pogs
|
||||
|
||||
// Tag previously was a legacy tunnel capnp struct but was deprecated. To help reduce the amount of changes imposed
|
||||
// by removing this simple struct, it was copied out of the capnp and provided here instead.
|
||||
type Tag struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
@@ -1,334 +0,0 @@
|
||||
package pogs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
"zombiezen.com/go/capnproto2/pogs"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
"zombiezen.com/go/capnproto2/server"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRetryAfterSeconds = 15
|
||||
)
|
||||
|
||||
type Authentication struct {
|
||||
Key string
|
||||
Email string
|
||||
OriginCAKey string
|
||||
}
|
||||
|
||||
func MarshalAuthentication(s tunnelrpc.Authentication, p *Authentication) error {
|
||||
return pogs.Insert(tunnelrpc.Authentication_TypeID, s.Struct, p)
|
||||
}
|
||||
|
||||
func UnmarshalAuthentication(s tunnelrpc.Authentication) (*Authentication, error) {
|
||||
p := new(Authentication)
|
||||
err := pogs.Extract(p, tunnelrpc.Authentication_TypeID, s.Struct)
|
||||
return p, err
|
||||
}
|
||||
|
||||
type TunnelRegistration struct {
|
||||
SuccessfulTunnelRegistration
|
||||
Err string
|
||||
PermanentFailure bool
|
||||
RetryAfterSeconds uint16
|
||||
}
|
||||
|
||||
type SuccessfulTunnelRegistration struct {
|
||||
Url string
|
||||
LogLines []string
|
||||
TunnelID string `capnp:"tunnelID"`
|
||||
EventDigest []byte
|
||||
ConnDigest []byte
|
||||
}
|
||||
|
||||
func NewSuccessfulTunnelRegistration(
|
||||
url string,
|
||||
logLines []string,
|
||||
tunnelID string,
|
||||
eventDigest []byte,
|
||||
connDigest []byte,
|
||||
) *TunnelRegistration {
|
||||
// Marshal nil will result in an error
|
||||
if logLines == nil {
|
||||
logLines = []string{}
|
||||
}
|
||||
return &TunnelRegistration{
|
||||
SuccessfulTunnelRegistration: SuccessfulTunnelRegistration{
|
||||
Url: url,
|
||||
LogLines: logLines,
|
||||
TunnelID: tunnelID,
|
||||
EventDigest: eventDigest,
|
||||
ConnDigest: connDigest,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Not calling this function Error() to avoid confusion with implementing error interface
|
||||
func (tr TunnelRegistration) DeserializeError() TunnelRegistrationError {
|
||||
if tr.Err != "" {
|
||||
err := fmt.Errorf(tr.Err)
|
||||
if tr.PermanentFailure {
|
||||
return NewPermanentRegistrationError(err)
|
||||
}
|
||||
retryAfterSeconds := tr.RetryAfterSeconds
|
||||
if retryAfterSeconds < defaultRetryAfterSeconds {
|
||||
retryAfterSeconds = defaultRetryAfterSeconds
|
||||
}
|
||||
return NewRetryableRegistrationError(err, retryAfterSeconds)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type TunnelRegistrationError interface {
|
||||
error
|
||||
Serialize() *TunnelRegistration
|
||||
IsPermanent() bool
|
||||
}
|
||||
|
||||
type PermanentRegistrationError struct {
|
||||
err string
|
||||
}
|
||||
|
||||
func NewPermanentRegistrationError(err error) TunnelRegistrationError {
|
||||
return &PermanentRegistrationError{
|
||||
err: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func (pre *PermanentRegistrationError) Error() string {
|
||||
return pre.err
|
||||
}
|
||||
|
||||
func (pre *PermanentRegistrationError) Serialize() *TunnelRegistration {
|
||||
return &TunnelRegistration{
|
||||
Err: pre.err,
|
||||
PermanentFailure: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (*PermanentRegistrationError) IsPermanent() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type RetryableRegistrationError struct {
|
||||
err string
|
||||
retryAfterSeconds uint16
|
||||
}
|
||||
|
||||
func NewRetryableRegistrationError(err error, retryAfterSeconds uint16) TunnelRegistrationError {
|
||||
return &RetryableRegistrationError{
|
||||
err: err.Error(),
|
||||
retryAfterSeconds: retryAfterSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
func (rre *RetryableRegistrationError) Error() string {
|
||||
return rre.err
|
||||
}
|
||||
|
||||
func (rre *RetryableRegistrationError) Serialize() *TunnelRegistration {
|
||||
return &TunnelRegistration{
|
||||
Err: rre.err,
|
||||
PermanentFailure: false,
|
||||
RetryAfterSeconds: rre.retryAfterSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
func (*RetryableRegistrationError) IsPermanent() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func MarshalTunnelRegistration(s tunnelrpc.TunnelRegistration, p *TunnelRegistration) error {
|
||||
return pogs.Insert(tunnelrpc.TunnelRegistration_TypeID, s.Struct, p)
|
||||
}
|
||||
|
||||
func UnmarshalTunnelRegistration(s tunnelrpc.TunnelRegistration) (*TunnelRegistration, error) {
|
||||
p := new(TunnelRegistration)
|
||||
err := pogs.Extract(p, tunnelrpc.TunnelRegistration_TypeID, s.Struct)
|
||||
return p, err
|
||||
}
|
||||
|
||||
type RegistrationOptions struct {
|
||||
ClientID string `capnp:"clientId"`
|
||||
Version string
|
||||
OS string `capnp:"os"`
|
||||
ExistingTunnelPolicy tunnelrpc.ExistingTunnelPolicy
|
||||
PoolName string `capnp:"poolName"`
|
||||
Tags []Tag
|
||||
ConnectionID uint8 `capnp:"connectionId"`
|
||||
OriginLocalIP string `capnp:"originLocalIp"`
|
||||
IsAutoupdated bool `capnp:"isAutoupdated"`
|
||||
RunFromTerminal bool `capnp:"runFromTerminal"`
|
||||
CompressionQuality uint64 `capnp:"compressionQuality"`
|
||||
UUID string `capnp:"uuid"`
|
||||
NumPreviousAttempts uint8
|
||||
Features []string
|
||||
}
|
||||
|
||||
func MarshalRegistrationOptions(s tunnelrpc.RegistrationOptions, p *RegistrationOptions) error {
|
||||
return pogs.Insert(tunnelrpc.RegistrationOptions_TypeID, s.Struct, p)
|
||||
}
|
||||
|
||||
func UnmarshalRegistrationOptions(s tunnelrpc.RegistrationOptions) (*RegistrationOptions, error) {
|
||||
p := new(RegistrationOptions)
|
||||
err := pogs.Extract(p, tunnelrpc.RegistrationOptions_TypeID, s.Struct)
|
||||
return p, err
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type ServerInfo struct {
|
||||
LocationName string
|
||||
}
|
||||
|
||||
func MarshalServerInfo(s tunnelrpc.ServerInfo, p *ServerInfo) error {
|
||||
return pogs.Insert(tunnelrpc.ServerInfo_TypeID, s.Struct, p)
|
||||
}
|
||||
|
||||
func UnmarshalServerInfo(s tunnelrpc.ServerInfo) (*ServerInfo, error) {
|
||||
p := new(ServerInfo)
|
||||
err := pogs.Extract(p, tunnelrpc.ServerInfo_TypeID, s.Struct)
|
||||
return p, err
|
||||
}
|
||||
|
||||
type TunnelServer interface {
|
||||
RegistrationServer
|
||||
RegisterTunnel(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) *TunnelRegistration
|
||||
GetServerInfo(ctx context.Context) (*ServerInfo, error)
|
||||
UnregisterTunnel(ctx context.Context, gracePeriodNanoSec int64) error
|
||||
Authenticate(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) (*AuthenticateResponse, error)
|
||||
ReconnectTunnel(ctx context.Context, jwt, eventDigest, connDigest []byte, hostname string, options *RegistrationOptions) (*TunnelRegistration, error)
|
||||
}
|
||||
|
||||
func TunnelServer_ServerToClient(s TunnelServer) tunnelrpc.TunnelServer {
|
||||
return tunnelrpc.TunnelServer_ServerToClient(TunnelServer_PogsImpl{RegistrationServer_PogsImpl{s}, s})
|
||||
}
|
||||
|
||||
type TunnelServer_PogsImpl struct {
|
||||
RegistrationServer_PogsImpl
|
||||
impl TunnelServer
|
||||
}
|
||||
|
||||
func (i TunnelServer_PogsImpl) RegisterTunnel(p tunnelrpc.TunnelServer_registerTunnel) error {
|
||||
originCert, err := p.Params.OriginCert()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostname, err := p.Params.Hostname()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options, err := p.Params.Options()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pogsOptions, err := UnmarshalRegistrationOptions(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
server.Ack(p.Options)
|
||||
registration := i.impl.RegisterTunnel(p.Ctx, originCert, hostname, pogsOptions)
|
||||
|
||||
result, err := p.Results.NewResult()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return MarshalTunnelRegistration(result, registration)
|
||||
}
|
||||
|
||||
func (i TunnelServer_PogsImpl) GetServerInfo(p tunnelrpc.TunnelServer_getServerInfo) error {
|
||||
server.Ack(p.Options)
|
||||
serverInfo, err := i.impl.GetServerInfo(p.Ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := p.Results.NewResult()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return MarshalServerInfo(result, serverInfo)
|
||||
}
|
||||
|
||||
func (i TunnelServer_PogsImpl) UnregisterTunnel(p tunnelrpc.TunnelServer_unregisterTunnel) error {
|
||||
gracePeriodNanoSec := p.Params.GracePeriodNanoSec()
|
||||
server.Ack(p.Options)
|
||||
return i.impl.UnregisterTunnel(p.Ctx, gracePeriodNanoSec)
|
||||
}
|
||||
|
||||
func (i TunnelServer_PogsImpl) ObsoleteDeclarativeTunnelConnect(p tunnelrpc.TunnelServer_obsoleteDeclarativeTunnelConnect) error {
|
||||
return fmt.Errorf("RPC to create declarative tunnel connection has been deprecated")
|
||||
}
|
||||
|
||||
type TunnelServer_PogsClient struct {
|
||||
RegistrationServer_PogsClient
|
||||
Client capnp.Client
|
||||
Conn *rpc.Conn
|
||||
}
|
||||
|
||||
func (c TunnelServer_PogsClient) Close() error {
|
||||
c.Client.Close()
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c TunnelServer_PogsClient) RegisterTunnel(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) *TunnelRegistration {
|
||||
client := tunnelrpc.TunnelServer{Client: c.Client}
|
||||
promise := client.RegisterTunnel(ctx, func(p tunnelrpc.TunnelServer_registerTunnel_Params) error {
|
||||
err := p.SetOriginCert(originCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.SetHostname(hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registrationOptions, err := p.NewOptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = MarshalRegistrationOptions(registrationOptions, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
retval, err := promise.Result().Struct()
|
||||
if err != nil {
|
||||
return NewRetryableRegistrationError(err, defaultRetryAfterSeconds).Serialize()
|
||||
}
|
||||
registration, err := UnmarshalTunnelRegistration(retval)
|
||||
if err != nil {
|
||||
return NewRetryableRegistrationError(err, defaultRetryAfterSeconds).Serialize()
|
||||
}
|
||||
return registration
|
||||
}
|
||||
|
||||
func (c TunnelServer_PogsClient) GetServerInfo(ctx context.Context) (*ServerInfo, error) {
|
||||
client := tunnelrpc.TunnelServer{Client: c.Client}
|
||||
promise := client.GetServerInfo(ctx, func(p tunnelrpc.TunnelServer_getServerInfo_Params) error {
|
||||
return nil
|
||||
})
|
||||
retval, err := promise.Result().Struct()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return UnmarshalServerInfo(retval)
|
||||
}
|
||||
|
||||
func (c TunnelServer_PogsClient) UnregisterTunnel(ctx context.Context, gracePeriodNanoSec int64) error {
|
||||
client := tunnelrpc.TunnelServer{Client: c.Client}
|
||||
promise := client.UnregisterTunnel(ctx, func(p tunnelrpc.TunnelServer_unregisterTunnel_Params) error {
|
||||
p.SetGracePeriodNanoSec(gracePeriodNanoSec)
|
||||
return nil
|
||||
})
|
||||
_, err := promise.Struct()
|
||||
return err
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package pogs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
)
|
||||
|
||||
const (
|
||||
testURL = "tunnel.example.com"
|
||||
testTunnelID = "asdfghjkl;"
|
||||
testRetryAfterSeconds = 19
|
||||
)
|
||||
|
||||
var (
|
||||
testErr = fmt.Errorf("Invalid credential")
|
||||
testLogLines = []string{"all", "working"}
|
||||
testEventDigest = []byte("asdf")
|
||||
testConnDigest = []byte("lkjh")
|
||||
)
|
||||
|
||||
// *PermanentRegistrationError implements TunnelRegistrationError
|
||||
var _ TunnelRegistrationError = (*PermanentRegistrationError)(nil)
|
||||
|
||||
// *RetryableRegistrationError implements TunnelRegistrationError
|
||||
var _ TunnelRegistrationError = (*RetryableRegistrationError)(nil)
|
||||
|
||||
func TestTunnelRegistration(t *testing.T) {
|
||||
testCases := []*TunnelRegistration{
|
||||
NewSuccessfulTunnelRegistration(testURL, testLogLines, testTunnelID, testEventDigest, testConnDigest),
|
||||
NewSuccessfulTunnelRegistration(testURL, nil, testTunnelID, testEventDigest, testConnDigest),
|
||||
NewPermanentRegistrationError(testErr).Serialize(),
|
||||
NewRetryableRegistrationError(testErr, testRetryAfterSeconds).Serialize(),
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
|
||||
assert.NoError(t, err)
|
||||
capnpEntity, err := tunnelrpc.NewTunnelRegistration(seg)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fatal("Couldn't initialize a new message")
|
||||
}
|
||||
err = MarshalTunnelRegistration(capnpEntity, testCase)
|
||||
if !assert.NoError(t, err, "testCase #%v failed to marshal", i) {
|
||||
continue
|
||||
}
|
||||
result, err := UnmarshalTunnelRegistration(capnpEntity)
|
||||
if !assert.NoError(t, err, "testCase #%v failed to unmarshal", i) {
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, testCase, result, "testCase index %v didn't preserve struct through marshalling and unmarshalling", i)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# Generate go.capnp.out with:
|
||||
# capnp compile -o- go.capnp > go.capnp.out
|
||||
# Must run inside this directory to preserve paths.
|
||||
|
||||
@0xd12a1c51fedd6c88;
|
||||
|
||||
annotation package(file) :Text;
|
||||
# The Go package name for the generated file.
|
||||
|
||||
annotation import(file) :Text;
|
||||
# The Go import path that the generated file is accessible from.
|
||||
# Used to generate import statements and check if two types are in the
|
||||
# same package.
|
||||
|
||||
annotation doc(struct, field, enum) :Text;
|
||||
# Adds a doc comment to the generated code.
|
||||
|
||||
annotation tag(enumerant) :Text;
|
||||
# Changes the string representation of the enum in the generated code.
|
||||
|
||||
annotation notag(enumerant) :Void;
|
||||
# Removes the string representation of the enum in the generated code.
|
||||
|
||||
annotation customtype(field) :Text;
|
||||
# OBSOLETE, not used by code generator.
|
||||
|
||||
annotation name(struct, field, union, enum, enumerant, interface, method, param, annotation, const, group) :Text;
|
||||
# Used to rename the element in the generated code.
|
||||
|
||||
$package("capnp");
|
||||
$import("zombiezen.com/go/capnproto2");
|
||||
@@ -0,0 +1,28 @@
|
||||
using Go = import "go.capnp";
|
||||
@0xb29021ef7421cc32;
|
||||
|
||||
$Go.package("proto");
|
||||
$Go.import("github.com/cloudflare/cloudflared/tunnelrpc");
|
||||
|
||||
|
||||
struct ConnectRequest @0xc47116a1045e4061 {
|
||||
dest @0 :Text;
|
||||
type @1 :ConnectionType;
|
||||
metadata @2 :List(Metadata);
|
||||
}
|
||||
|
||||
enum ConnectionType @0xc52e1bac26d379c8 {
|
||||
http @0;
|
||||
websocket @1;
|
||||
tcp @2;
|
||||
}
|
||||
|
||||
struct Metadata @0xe1446b97bfd1cd37 {
|
||||
key @0 :Text;
|
||||
val @1 :Text;
|
||||
}
|
||||
|
||||
struct ConnectResponse @0xb1032ec91cef8727 {
|
||||
error @0 :Text;
|
||||
metadata @1 :List(Metadata);
|
||||
}
|
||||
+29
-29
@@ -1,6 +1,6 @@
|
||||
// Code generated by capnpc-go. DO NOT EDIT.
|
||||
|
||||
package schema
|
||||
package proto
|
||||
|
||||
import (
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
@@ -357,34 +357,34 @@ func (p ConnectResponse_Promise) Struct() (ConnectResponse, error) {
|
||||
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"
|
||||
const schema_b29021ef7421cc32 = "x\xda\xb4\x911k\x14A\x1c\xc5\xdf\x9b\xcde-\x0e" +
|
||||
"\xf7\x86KlT\xc2\x05Q\x13\xdc\x8b\xc9\x09\xa2 \x1c" +
|
||||
"\x18A%\xc1\x9b`mX7\x83\x09w\xee\xce\xed\xce" +
|
||||
"\x19\xee\x13\xd8\xda\x89\xa5\xbd \x09X\xdb((h!" +
|
||||
"\x16\xd6\x0a66\xf9\x04\xb22\x0b\x9b\x83\x90B\x04\xbb" +
|
||||
"\xe1\xcd\x9by\xbf\xff\xff5\xbeu\xc5r\xed\x11\x01\xd5" +
|
||||
"\xa8M\x17\x17\x9e\x1e\x9c\xf9\xd8\xf6\xf6 C\x16+\x9f" +
|
||||
"Z\xf6\xa0\xf5l\x1f5\xe1\x03\xcb/~Q\xbe\xf1\x01" +
|
||||
"\xb9\xb7\x0b\x16Q\xf7\xc1\xd4\xcbS\xc3wP!\x8fZ" +
|
||||
";-\xfe`\xf3\x06}\xa0y\x8d\xaf\xc1\xe2\xc3\xf8\xeb" +
|
||||
"\xf9W\xa7\xdb\xef!C11\x83\x9d\x9f\xceI\xf7\xa8" +
|
||||
"\xf9\x9b\xf7\xc0\xe2\xea\xe7/o\x9f\xf7W\xbf\x1fC\xd0" +
|
||||
"\x99\x15\xfbl\x86\xa5yA8\x08;J\x12=\xc8\xcc" +
|
||||
"t\xbcd\xb2\xd4\xa6K\xc3\xd1N\xbc\xf9X\xdbh+" +
|
||||
"\xb2\xd1f\xa9\xc5\xe9\xa0\x1dG&1\xd7o\xa6I\xa2" +
|
||||
"c\xbb\xa1s\x13\xa4I\xae{\xa4:\xe1M\x01S\x04" +
|
||||
"\xe4\xc2\x0a\xa0\xceyT\x97\x05%9C'\x86w\x01" +
|
||||
"u\xc9\xa3\xba-8\xa7\xb3,\xcdX\x87`\x1d,\xaa" +
|
||||
"\x14\x00<\x09\xf6<\xb21\xa1\x07\x9d\xf8\xaf\x80\xc3\x91" +
|
||||
"\xafs\xeb\xf8\xea\x87|\xb7\x16\x01\xd5\xf5\xa8\xd6\x04+" +
|
||||
"\xbc;N[\xf5\xa8z\x82Rp\x86\x02\x90\xeb\x8ey" +
|
||||
"\xcd\xa3\xda\x16\x0c\xb6tn+\xe4\xc0\x8e\x8df0)" +
|
||||
"\x03d\xf0_'\xd9I\x93\xfb\xfe\xd8\x94\x9b\xae\x97p" +
|
||||
"g\x17\xdd\x87rv\x03\xa0\x90r\x1e\x08\xb6\xad5\xc5" +
|
||||
"\xae~\x98\xa7q_\x83\xd6\xb7\xb19\x8c\xab\xfdU\xdc" +
|
||||
"\xba\xb6s\xe5\xc5\x91J\xe7\x8f\xab\xd4\x89\x17=\xaa+" +
|
||||
"\x82~_\x8f\xab\xed\xf8O\xa2Au\xfe\x13\x00\x00\xff" +
|
||||
"\xff\x1d\xce\xd1\xb0"
|
||||
|
||||
func init() {
|
||||
schemas.Register(schema_b29021ef7421cc32,
|
||||
@@ -1,15 +1,23 @@
|
||||
using Go = import "go.capnp";
|
||||
@0xdb8274f9144abc7e;
|
||||
$Go.package("tunnelrpc");
|
||||
$Go.package("proto");
|
||||
$Go.import("github.com/cloudflare/cloudflared/tunnelrpc");
|
||||
|
||||
struct Authentication {
|
||||
# === DEPRECATED Legacy Tunnel Authentication and Registration methods/servers ===
|
||||
#
|
||||
# These structs and interfaces are no longer used but it is important to keep
|
||||
# them around to make sure backwards compatibility within the rpc protocol is
|
||||
# maintained.
|
||||
|
||||
struct Authentication @0xc082ef6e0d42ed1d {
|
||||
# DEPRECATED: Legacy tunnel authentication mechanism
|
||||
key @0 :Text;
|
||||
email @1 :Text;
|
||||
originCAKey @2 :Text;
|
||||
}
|
||||
|
||||
struct TunnelRegistration {
|
||||
struct TunnelRegistration @0xf41a0f001ad49e46 {
|
||||
# DEPRECATED: Legacy tunnel authentication mechanism
|
||||
err @0 :Text;
|
||||
# the url to access the tunnel
|
||||
url @1 :Text;
|
||||
@@ -27,7 +35,9 @@ struct TunnelRegistration {
|
||||
connDigest @7 :Data;
|
||||
}
|
||||
|
||||
struct RegistrationOptions {
|
||||
struct RegistrationOptions @0xc793e50592935b4a {
|
||||
# DEPRECATED: Legacy tunnel authentication mechanism
|
||||
|
||||
# The tunnel client's unique identifier, used to verify a reconnection.
|
||||
clientId @0 :Text;
|
||||
# Information about the running binary.
|
||||
@@ -56,29 +66,51 @@ struct RegistrationOptions {
|
||||
features @13 :List(Text);
|
||||
}
|
||||
|
||||
struct Tag {
|
||||
name @0 :Text;
|
||||
value @1 :Text;
|
||||
}
|
||||
enum ExistingTunnelPolicy @0x84cb9536a2cf6d3c {
|
||||
# DEPRECATED: Legacy tunnel registration mechanism
|
||||
|
||||
enum ExistingTunnelPolicy {
|
||||
ignore @0;
|
||||
disconnect @1;
|
||||
balance @2;
|
||||
}
|
||||
|
||||
struct ServerInfo {
|
||||
struct ServerInfo @0xf2c68e2547ec3866 {
|
||||
# DEPRECATED: Legacy tunnel registration mechanism
|
||||
|
||||
locationName @0 :Text;
|
||||
}
|
||||
|
||||
struct AuthenticateResponse {
|
||||
struct AuthenticateResponse @0x82c325a07ad22a65 {
|
||||
# DEPRECATED: Legacy tunnel registration mechanism
|
||||
|
||||
permanentErr @0 :Text;
|
||||
retryableErr @1 :Text;
|
||||
jwt @2 :Data;
|
||||
hoursUntilRefresh @3 :UInt8;
|
||||
}
|
||||
|
||||
struct ClientInfo {
|
||||
interface TunnelServer @0xea58385c65416035 extends (RegistrationServer) {
|
||||
# DEPRECATED: Legacy tunnel authentication server
|
||||
|
||||
registerTunnel @0 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
|
||||
getServerInfo @1 () -> (result :ServerInfo);
|
||||
unregisterTunnel @2 (gracePeriodNanoSec :Int64) -> ();
|
||||
# obsoleteDeclarativeTunnelConnect RPC deprecated in TUN-3019
|
||||
obsoleteDeclarativeTunnelConnect @3 () -> ();
|
||||
authenticate @4 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :AuthenticateResponse);
|
||||
reconnectTunnel @5 (jwt :Data, eventDigest :Data, connDigest :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
|
||||
}
|
||||
|
||||
struct Tag @0xcbd96442ae3bb01a {
|
||||
# DEPRECATED: Legacy tunnel additional HTTP header mechanism
|
||||
|
||||
name @0 :Text;
|
||||
value @1 :Text;
|
||||
}
|
||||
|
||||
# === End DEPRECATED Objects ===
|
||||
|
||||
struct ClientInfo @0x83ced0145b2f114b {
|
||||
# The tunnel client's unique identifier, used to verify a reconnection.
|
||||
clientId @0 :Data;
|
||||
# Set of features this cloudflared knows it supports
|
||||
@@ -89,7 +121,7 @@ struct ClientInfo {
|
||||
arch @3 :Text;
|
||||
}
|
||||
|
||||
struct ConnectionOptions {
|
||||
struct ConnectionOptions @0xb4bf9861fe035d04 {
|
||||
# client details
|
||||
client @0 :ClientInfo;
|
||||
# origin LAN IP
|
||||
@@ -102,21 +134,21 @@ struct ConnectionOptions {
|
||||
numPreviousAttempts @4 :UInt8;
|
||||
}
|
||||
|
||||
struct ConnectionResponse {
|
||||
struct ConnectionResponse @0xdbaa9d03d52b62dc {
|
||||
result :union {
|
||||
error @0 :ConnectionError;
|
||||
connectionDetails @1 :ConnectionDetails;
|
||||
}
|
||||
}
|
||||
|
||||
struct ConnectionError {
|
||||
struct ConnectionError @0xf5f383d2785edb86 {
|
||||
cause @0 :Text;
|
||||
# How long should this connection wait to retry in ns
|
||||
retryAfter @1 :Int64;
|
||||
shouldRetry @2 :Bool;
|
||||
}
|
||||
|
||||
struct ConnectionDetails {
|
||||
struct ConnectionDetails @0xb5f39f082b9ac18a {
|
||||
# identifier of this connection
|
||||
uuid @0 :Data;
|
||||
# airport code of the colo where this connection landed
|
||||
@@ -125,39 +157,29 @@ struct ConnectionDetails {
|
||||
tunnelIsRemotelyManaged @2: Bool;
|
||||
}
|
||||
|
||||
struct TunnelAuth {
|
||||
struct TunnelAuth @0x9496331ab9cd463f {
|
||||
accountTag @0 :Text;
|
||||
tunnelSecret @1 :Data;
|
||||
}
|
||||
|
||||
interface RegistrationServer {
|
||||
interface RegistrationServer @0xf71695ec7fe85497 {
|
||||
registerConnection @0 (auth :TunnelAuth, tunnelId :Data, connIndex :UInt8, options :ConnectionOptions) -> (result :ConnectionResponse);
|
||||
unregisterConnection @1 () -> ();
|
||||
updateLocalConfiguration @2 (config :Data) -> ();
|
||||
}
|
||||
|
||||
interface TunnelServer extends (RegistrationServer) {
|
||||
registerTunnel @0 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
|
||||
getServerInfo @1 () -> (result :ServerInfo);
|
||||
unregisterTunnel @2 (gracePeriodNanoSec :Int64) -> ();
|
||||
# obsoleteDeclarativeTunnelConnect RPC deprecated in TUN-3019
|
||||
obsoleteDeclarativeTunnelConnect @3 () -> ();
|
||||
authenticate @4 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :AuthenticateResponse);
|
||||
reconnectTunnel @5 (jwt :Data, eventDigest :Data, connDigest :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
|
||||
}
|
||||
|
||||
struct RegisterUdpSessionResponse {
|
||||
struct RegisterUdpSessionResponse @0xab6d5210c1f26687 {
|
||||
err @0 :Text;
|
||||
spans @1 :Data;
|
||||
}
|
||||
|
||||
interface SessionManager {
|
||||
interface SessionManager @0x839445a59fb01686 {
|
||||
# Let the edge decide closeAfterIdle to make sure cloudflared doesn't close session before the edge closes its side
|
||||
registerUdpSession @0 (sessionId :Data, dstIp :Data, dstPort :UInt16, closeAfterIdleHint :Int64, traceContext :Text = "") -> (result :RegisterUdpSessionResponse);
|
||||
unregisterUdpSession @1 (sessionId :Data, message :Text) -> ();
|
||||
}
|
||||
|
||||
struct UpdateConfigurationResponse {
|
||||
struct UpdateConfigurationResponse @0xdb58ff694ba05cf9 {
|
||||
# Latest configuration that was applied successfully. The err field might be populated at the same time to indicate
|
||||
# that cloudflared is using an older configuration because the latest cannot be applied
|
||||
latestAppliedVersion @0 :Int32;
|
||||
@@ -166,8 +188,8 @@ struct UpdateConfigurationResponse {
|
||||
}
|
||||
|
||||
# ConfigurationManager defines RPC to manage cloudflared configuration remotely
|
||||
interface ConfigurationManager {
|
||||
interface ConfigurationManager @0xb48edfbdaa25db04 {
|
||||
updateConfiguration @0 (version :Int32, config :Data) -> (result: UpdateConfigurationResponse);
|
||||
}
|
||||
|
||||
interface CloudflaredServer extends(SessionManager, ConfigurationManager) {}
|
||||
interface CloudflaredServer @0xf548cef9dea2a4a1 extends(SessionManager, ConfigurationManager) {}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,89 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/metrics"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// CloudflaredClient calls capnp rpc methods of SessionManager and ConfigurationManager.
|
||||
type CloudflaredClient struct {
|
||||
client pogs.CloudflaredServer_PogsClient
|
||||
transport rpc.Transport
|
||||
requestTimeout time.Duration
|
||||
}
|
||||
|
||||
func NewCloudflaredClient(ctx context.Context, stream io.ReadWriteCloser, requestTimeout time.Duration) (*CloudflaredClient, error) {
|
||||
n, err := stream.Write(rpcStreamProtocolSignature[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n != len(rpcStreamProtocolSignature) {
|
||||
return nil, fmt.Errorf("expect to write %d bytes for RPC stream protocol signature, wrote %d", len(rpcStreamProtocolSignature), n)
|
||||
}
|
||||
transport := tunnelrpc.SafeTransport(stream)
|
||||
conn := rpc.NewConn(transport)
|
||||
client := pogs.NewCloudflaredServer_PogsClient(conn.Bootstrap(ctx), conn)
|
||||
return &CloudflaredClient{
|
||||
client: client,
|
||||
transport: transport,
|
||||
requestTimeout: requestTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *CloudflaredClient) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfterHint time.Duration, traceContext string) (*pogs.RegisterUdpSessionResponse, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.requestTimeout)
|
||||
defer cancel()
|
||||
defer metrics.CapnpMetrics.ClientOperations.WithLabelValues(metrics.Cloudflared, metrics.OperationRegisterUdpSession).Inc()
|
||||
timer := metrics.NewClientOperationLatencyObserver(metrics.Cloudflared, metrics.OperationRegisterUdpSession)
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
resp, err := c.client.RegisterUdpSession(ctx, sessionID, dstIP, dstPort, closeIdleAfterHint, traceContext)
|
||||
if err != nil {
|
||||
metrics.CapnpMetrics.ClientFailures.WithLabelValues(metrics.Cloudflared, metrics.OperationRegisterUdpSession).Inc()
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *CloudflaredClient) UnregisterUdpSession(ctx context.Context, sessionID uuid.UUID, message string) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.requestTimeout)
|
||||
defer cancel()
|
||||
defer metrics.CapnpMetrics.ClientOperations.WithLabelValues(metrics.Cloudflared, metrics.OperationUnregisterUdpSession).Inc()
|
||||
timer := metrics.NewClientOperationLatencyObserver(metrics.Cloudflared, metrics.OperationUnregisterUdpSession)
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
err := c.client.UnregisterUdpSession(ctx, sessionID, message)
|
||||
if err != nil {
|
||||
metrics.CapnpMetrics.ClientFailures.WithLabelValues(metrics.Cloudflared, metrics.OperationUnregisterUdpSession).Inc()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CloudflaredClient) UpdateConfiguration(ctx context.Context, version int32, config []byte) (*pogs.UpdateConfigurationResponse, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.requestTimeout)
|
||||
defer cancel()
|
||||
defer metrics.CapnpMetrics.ClientOperations.WithLabelValues(metrics.Cloudflared, metrics.OperationUpdateConfiguration).Inc()
|
||||
timer := metrics.NewClientOperationLatencyObserver(metrics.Cloudflared, metrics.OperationUpdateConfiguration)
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
resp, err := c.client.UpdateConfiguration(ctx, version, config)
|
||||
if err != nil {
|
||||
metrics.CapnpMetrics.ClientFailures.WithLabelValues(metrics.Cloudflared, metrics.OperationUpdateConfiguration).Inc()
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *CloudflaredClient) Close() {
|
||||
_ = c.client.Close()
|
||||
_ = c.transport.Close()
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// HandleRequestFunc wraps the proxied request from the upstream and also provides methods on the stream to
|
||||
// handle the response back.
|
||||
type HandleRequestFunc = func(ctx context.Context, stream *RequestServerStream) error
|
||||
|
||||
// CloudflaredServer provides a handler interface for a client to provide methods to handle the different types of
|
||||
// requests that can be communicated by the stream.
|
||||
type CloudflaredServer struct {
|
||||
handleRequest HandleRequestFunc
|
||||
sessionManager pogs.SessionManager
|
||||
configManager pogs.ConfigurationManager
|
||||
responseTimeout time.Duration
|
||||
}
|
||||
|
||||
func NewCloudflaredServer(handleRequest HandleRequestFunc, sessionManager pogs.SessionManager, configManager pogs.ConfigurationManager, responseTimeout time.Duration) *CloudflaredServer {
|
||||
return &CloudflaredServer{
|
||||
handleRequest: handleRequest,
|
||||
sessionManager: sessionManager,
|
||||
configManager: configManager,
|
||||
responseTimeout: responseTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
// Serve executes the defined handlers in ServerStream on the provided stream if it is a proper RPC stream with the
|
||||
// correct preamble protocol signature.
|
||||
func (s *CloudflaredServer) Serve(ctx context.Context, stream io.ReadWriteCloser) error {
|
||||
signature, err := determineProtocol(stream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch signature {
|
||||
case dataStreamProtocolSignature:
|
||||
return s.handleRequest(ctx, &RequestServerStream{stream})
|
||||
case rpcStreamProtocolSignature:
|
||||
return s.handleRPC(ctx, stream)
|
||||
default:
|
||||
return fmt.Errorf("unknown protocol %v", signature)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CloudflaredServer) handleRPC(ctx context.Context, stream io.ReadWriteCloser) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, s.responseTimeout)
|
||||
defer cancel()
|
||||
transport := tunnelrpc.SafeTransport(stream)
|
||||
defer transport.Close()
|
||||
|
||||
main := pogs.CloudflaredServer_ServerToClient(s.sessionManager, s.configManager)
|
||||
rpcConn := rpc.NewConn(transport, rpc.MainInterface(main.Client))
|
||||
defer rpcConn.Close()
|
||||
|
||||
// We ignore the errors here because if cloudflared fails to handle a request, we will just move on.
|
||||
select {
|
||||
case <-rpcConn.Done():
|
||||
case <-ctx.Done():
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// protocolSignature defines the first 6 bytes of the stream, which is used to distinguish the type of stream. It
|
||||
// ensures whoever performs a handshake does not write data before writing the metadata.
|
||||
type protocolSignature [6]byte
|
||||
|
||||
var (
|
||||
// dataStreamProtocolSignature is a custom protocol signature for data stream
|
||||
dataStreamProtocolSignature = protocolSignature{0x0A, 0x36, 0xCD, 0x12, 0xA1, 0x3E}
|
||||
|
||||
// rpcStreamProtocolSignature is a custom protocol signature for RPC stream
|
||||
rpcStreamProtocolSignature = protocolSignature{0x52, 0xBB, 0x82, 0x5C, 0xDB, 0x65}
|
||||
|
||||
errDataStreamNotSupported = fmt.Errorf("data protocol not supported")
|
||||
errRPCStreamNotSupported = fmt.Errorf("rpc protocol not supported")
|
||||
)
|
||||
|
||||
type protocolVersion string
|
||||
|
||||
const (
|
||||
protocolV1 protocolVersion = "01"
|
||||
|
||||
protocolVersionLength = 2
|
||||
)
|
||||
|
||||
// determineProtocol reads the first 6 bytes from the stream to determine which protocol is spoken by the client.
|
||||
// The protocols are magic byte arrays understood by both sides of the stream.
|
||||
func determineProtocol(stream io.Reader) (protocolSignature, error) {
|
||||
signature, err := readSignature(stream)
|
||||
if err != nil {
|
||||
return protocolSignature{}, err
|
||||
}
|
||||
switch signature {
|
||||
case dataStreamProtocolSignature:
|
||||
return dataStreamProtocolSignature, nil
|
||||
case rpcStreamProtocolSignature:
|
||||
return rpcStreamProtocolSignature, nil
|
||||
default:
|
||||
return protocolSignature{}, fmt.Errorf("unknown signature %v", signature)
|
||||
}
|
||||
}
|
||||
|
||||
func writeDataStreamPreamble(stream io.Writer) error {
|
||||
if err := writeSignature(stream, dataStreamProtocolSignature); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeVersion(stream)
|
||||
}
|
||||
|
||||
func writeVersion(stream io.Writer) error {
|
||||
_, err := stream.Write([]byte(protocolV1)[:protocolVersionLength])
|
||||
return err
|
||||
}
|
||||
|
||||
func readVersion(stream io.Reader) (string, error) {
|
||||
version := make([]byte, protocolVersionLength)
|
||||
_, err := stream.Read(version)
|
||||
return string(version), err
|
||||
}
|
||||
|
||||
func readSignature(stream io.Reader) (protocolSignature, error) {
|
||||
var signature protocolSignature
|
||||
if _, err := io.ReadFull(stream, signature[:]); err != nil {
|
||||
return protocolSignature{}, err
|
||||
}
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
func writeSignature(stream io.Writer, signature protocolSignature) error {
|
||||
_, err := stream.Write(signature[:])
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// RequestClientStream is a stream to provide requests to the server. This operation is typically driven by the edge service.
|
||||
type RequestClientStream struct {
|
||||
io.ReadWriteCloser
|
||||
}
|
||||
|
||||
// WriteConnectRequestData writes requestMeta to a stream.
|
||||
func (rcs *RequestClientStream) WriteConnectRequestData(dest string, connectionType pogs.ConnectionType, metadata ...pogs.Metadata) error {
|
||||
connectRequest := &pogs.ConnectRequest{
|
||||
Dest: dest,
|
||||
Type: connectionType,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
msg, err := connectRequest.ToPogs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeDataStreamPreamble(rcs); err != nil {
|
||||
return err
|
||||
}
|
||||
return capnp.NewEncoder(rcs).Encode(msg)
|
||||
}
|
||||
|
||||
// ReadConnectResponseData reads the response from the rpc stream to a ConnectResponse.
|
||||
func (rcs *RequestClientStream) ReadConnectResponseData() (*pogs.ConnectResponse, error) {
|
||||
signature, err := determineProtocol(rcs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if signature != dataStreamProtocolSignature {
|
||||
return nil, fmt.Errorf("wrong protocol signature %v", signature)
|
||||
}
|
||||
|
||||
// This is a NO-OP for now. We could cause a branching if we wanted to use multiple versions.
|
||||
if _, err := readVersion(rcs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, err := capnp.NewDecoder(rcs).Decode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &pogs.ConnectResponse{}
|
||||
if err := r.FromPogs(msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// RequestServerStream is a stream to serve requests
|
||||
type RequestServerStream struct {
|
||||
io.ReadWriteCloser
|
||||
}
|
||||
|
||||
// ReadConnectRequestData reads the handshake data from a QUIC stream.
|
||||
func (rss *RequestServerStream) ReadConnectRequestData() (*pogs.ConnectRequest, error) {
|
||||
// This is a NO-OP for now. We could cause a branching if we wanted to use multiple versions.
|
||||
if _, err := readVersion(rss); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, err := capnp.NewDecoder(rss).Decode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &pogs.ConnectRequest{}
|
||||
if err := r.FromPogs(msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// WriteConnectResponseData writes response to a QUIC stream.
|
||||
func (rss *RequestServerStream) WriteConnectResponseData(respErr error, metadata ...pogs.Metadata) error {
|
||||
var connectResponse *pogs.ConnectResponse
|
||||
if respErr != nil {
|
||||
connectResponse = &pogs.ConnectResponse{
|
||||
Error: respErr.Error(),
|
||||
}
|
||||
} else {
|
||||
connectResponse = &pogs.ConnectResponse{
|
||||
Metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
msg, err := connectResponse.ToPogs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeDataStreamPreamble(rss); err != nil {
|
||||
return err
|
||||
}
|
||||
return capnp.NewEncoder(rss).Encode(msg)
|
||||
}
|
||||
@@ -11,11 +11,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -26,14 +25,14 @@ func TestConnectRequestData(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
hostname string
|
||||
connectionType ConnectionType
|
||||
metadata []Metadata
|
||||
connectionType pogs.ConnectionType
|
||||
metadata []pogs.Metadata
|
||||
}{
|
||||
{
|
||||
name: "Signature verified and request metadata is unmarshaled and read correctly",
|
||||
hostname: "tunnel.com",
|
||||
connectionType: ConnectionTypeHTTP,
|
||||
metadata: []Metadata{
|
||||
connectionType: pogs.ConnectionTypeHTTP,
|
||||
metadata: []pogs.Metadata{
|
||||
{
|
||||
Key: "key",
|
||||
Val: "1234",
|
||||
@@ -47,10 +46,10 @@ func TestConnectRequestData(t *testing.T) {
|
||||
reqClientStream := RequestClientStream{noopCloser{b}}
|
||||
err := reqClientStream.WriteConnectRequestData(test.hostname, test.connectionType, test.metadata...)
|
||||
require.NoError(t, err)
|
||||
protocol, err := DetermineProtocol(b)
|
||||
require.NoError(t, err)
|
||||
reqServerStream, err := NewRequestServerStream(noopCloser{b}, protocol)
|
||||
protocol, err := determineProtocol(b)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, dataStreamProtocolSignature, protocol)
|
||||
reqServerStream := RequestServerStream{&noopCloser{b}}
|
||||
|
||||
reqMeta, err := reqServerStream.ReadConnectRequestData()
|
||||
require.NoError(t, err)
|
||||
@@ -66,11 +65,11 @@ func TestConnectResponseMeta(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
err error
|
||||
metadata []Metadata
|
||||
metadata []pogs.Metadata
|
||||
}{
|
||||
{
|
||||
name: "Signature verified and response metadata is unmarshaled and read correctly",
|
||||
metadata: []Metadata{
|
||||
metadata: []pogs.Metadata{
|
||||
{
|
||||
Key: "key",
|
||||
Val: "1234",
|
||||
@@ -80,7 +79,7 @@ func TestConnectResponseMeta(t *testing.T) {
|
||||
{
|
||||
name: "If error is not empty, other fields should be blank",
|
||||
err: errors.New("something happened"),
|
||||
metadata: []Metadata{
|
||||
metadata: []pogs.Metadata{
|
||||
{
|
||||
Key: "key",
|
||||
Val: "1234",
|
||||
@@ -142,22 +141,18 @@ func TestRegisterUdpSession(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
logger := zerolog.Nop()
|
||||
clientStream, serverStream := newMockRPCStreams()
|
||||
sessionRegisteredChan := make(chan struct{})
|
||||
go func() {
|
||||
protocol, err := DetermineProtocol(serverStream)
|
||||
assert.NoError(t, err)
|
||||
rpcServerStream, err := NewRPCServerStream(serverStream, protocol)
|
||||
assert.NoError(t, err)
|
||||
err = rpcServerStream.Serve(test.sessionRPCServer, nil, &logger)
|
||||
ss := NewCloudflaredServer(nil, test.sessionRPCServer, nil, 10*time.Second)
|
||||
err := ss.Serve(context.Background(), serverStream)
|
||||
assert.NoError(t, err)
|
||||
|
||||
serverStream.Close()
|
||||
close(sessionRegisteredChan)
|
||||
}()
|
||||
|
||||
rpcClientStream, err := NewRPCClientStream(context.Background(), clientStream, 5*time.Second, &logger)
|
||||
rpcClientStream, err := NewCloudflaredClient(context.Background(), clientStream, 5*time.Second)
|
||||
assert.NoError(t, err)
|
||||
|
||||
reg, err := rpcClientStream.RegisterUdpSession(context.Background(), test.sessionRPCServer.sessionID, test.sessionRPCServer.dstIP, test.sessionRPCServer.dstPort, testCloseIdleAfterHint, test.sessionRPCServer.traceContext)
|
||||
@@ -192,14 +187,10 @@ func TestManageConfiguration(t *testing.T) {
|
||||
config: config,
|
||||
}
|
||||
|
||||
logger := zerolog.Nop()
|
||||
updatedChan := make(chan struct{})
|
||||
go func() {
|
||||
protocol, err := DetermineProtocol(serverStream)
|
||||
assert.NoError(t, err)
|
||||
rpcServerStream, err := NewRPCServerStream(serverStream, protocol)
|
||||
assert.NoError(t, err)
|
||||
err = rpcServerStream.Serve(nil, configRPCServer, &logger)
|
||||
server := NewCloudflaredServer(nil, nil, configRPCServer, 10*time.Second)
|
||||
err := server.Serve(context.Background(), serverStream)
|
||||
assert.NoError(t, err)
|
||||
|
||||
serverStream.Close()
|
||||
@@ -208,7 +199,7 @@ func TestManageConfiguration(t *testing.T) {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
rpcClientStream, err := NewRPCClientStream(ctx, clientStream, 5*time.Second, &logger)
|
||||
rpcClientStream, err := NewCloudflaredClient(ctx, clientStream, 5*time.Second)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err := rpcClientStream.UpdateConfiguration(ctx, version, config)
|
||||
@@ -230,7 +221,7 @@ type mockSessionRPCServer struct {
|
||||
traceContext string
|
||||
}
|
||||
|
||||
func (s mockSessionRPCServer) RegisterUdpSession(_ context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfter time.Duration, traceContext string) (*tunnelpogs.RegisterUdpSessionResponse, error) {
|
||||
func (s mockSessionRPCServer) RegisterUdpSession(_ context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfter time.Duration, traceContext string) (*pogs.RegisterUdpSessionResponse, error) {
|
||||
if s.sessionID != sessionID {
|
||||
return nil, fmt.Errorf("expect session ID %s, got %s", s.sessionID, sessionID)
|
||||
}
|
||||
@@ -246,7 +237,7 @@ func (s mockSessionRPCServer) RegisterUdpSession(_ context.Context, sessionID uu
|
||||
if s.traceContext != traceContext {
|
||||
return nil, fmt.Errorf("expect traceContext %s, got %s", s.traceContext, traceContext)
|
||||
}
|
||||
return &tunnelpogs.RegisterUdpSessionResponse{}, nil
|
||||
return &pogs.RegisterUdpSessionResponse{}, nil
|
||||
}
|
||||
|
||||
func (s mockSessionRPCServer) UnregisterUdpSession(_ context.Context, sessionID uuid.UUID, message string) error {
|
||||
@@ -264,18 +255,18 @@ type mockConfigRPCServer struct {
|
||||
config []byte
|
||||
}
|
||||
|
||||
func (s mockConfigRPCServer) UpdateConfiguration(_ context.Context, version int32, config []byte) *tunnelpogs.UpdateConfigurationResponse {
|
||||
func (s mockConfigRPCServer) UpdateConfiguration(_ context.Context, version int32, config []byte) *pogs.UpdateConfigurationResponse {
|
||||
if s.version != version {
|
||||
return &tunnelpogs.UpdateConfigurationResponse{
|
||||
return &pogs.UpdateConfigurationResponse{
|
||||
Err: fmt.Errorf("expect version %d, got %d", s.version, version),
|
||||
}
|
||||
}
|
||||
if !bytes.Equal(s.config, config) {
|
||||
return &tunnelpogs.UpdateConfigurationResponse{
|
||||
return &pogs.UpdateConfigurationResponse{
|
||||
Err: fmt.Errorf("expect config %v, got %v", s.config, config),
|
||||
}
|
||||
}
|
||||
return &tunnelpogs.UpdateConfigurationResponse{LastAppliedVersion: version}
|
||||
return &pogs.UpdateConfigurationResponse{LastAppliedVersion: version}
|
||||
}
|
||||
|
||||
type mockRPCStream struct {
|
||||
@@ -0,0 +1,73 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/metrics"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// SessionClient calls capnp rpc methods of SessionManager.
|
||||
type SessionClient struct {
|
||||
client pogs.SessionManager_PogsClient
|
||||
transport rpc.Transport
|
||||
requestTimeout time.Duration
|
||||
}
|
||||
|
||||
func NewSessionClient(ctx context.Context, stream io.ReadWriteCloser, requestTimeout time.Duration) (*SessionClient, error) {
|
||||
n, err := stream.Write(rpcStreamProtocolSignature[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n != len(rpcStreamProtocolSignature) {
|
||||
return nil, fmt.Errorf("expect to write %d bytes for RPC stream protocol signature, wrote %d", len(rpcStreamProtocolSignature), n)
|
||||
}
|
||||
transport := tunnelrpc.SafeTransport(stream)
|
||||
conn := rpc.NewConn(transport)
|
||||
return &SessionClient{
|
||||
client: pogs.NewSessionManager_PogsClient(conn.Bootstrap(ctx), conn),
|
||||
transport: transport,
|
||||
requestTimeout: requestTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *SessionClient) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfterHint time.Duration, traceContext string) (*pogs.RegisterUdpSessionResponse, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.requestTimeout)
|
||||
defer cancel()
|
||||
defer metrics.CapnpMetrics.ClientOperations.WithLabelValues(metrics.SessionManager, metrics.OperationRegisterUdpSession).Inc()
|
||||
timer := metrics.NewClientOperationLatencyObserver(metrics.SessionManager, metrics.OperationRegisterUdpSession)
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
resp, err := c.client.RegisterUdpSession(ctx, sessionID, dstIP, dstPort, closeIdleAfterHint, traceContext)
|
||||
if err != nil {
|
||||
metrics.CapnpMetrics.ClientFailures.WithLabelValues(metrics.SessionManager, metrics.OperationRegisterUdpSession).Inc()
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *SessionClient) UnregisterUdpSession(ctx context.Context, sessionID uuid.UUID, message string) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.requestTimeout)
|
||||
defer cancel()
|
||||
defer metrics.CapnpMetrics.ClientOperations.WithLabelValues(metrics.SessionManager, metrics.OperationUnregisterUdpSession).Inc()
|
||||
timer := metrics.NewClientOperationLatencyObserver(metrics.SessionManager, metrics.OperationUnregisterUdpSession)
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
err := c.client.UnregisterUdpSession(ctx, sessionID, message)
|
||||
if err != nil {
|
||||
metrics.CapnpMetrics.ClientFailures.WithLabelValues(metrics.SessionManager, metrics.OperationUnregisterUdpSession).Inc()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *SessionClient) Close() {
|
||||
_ = c.client.Close()
|
||||
_ = c.transport.Close()
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// SessionManagerServer handles streams with the SessionManager RPCs.
|
||||
type SessionManagerServer struct {
|
||||
sessionManager pogs.SessionManager
|
||||
responseTimeout time.Duration
|
||||
}
|
||||
|
||||
func NewSessionManagerServer(sessionManager pogs.SessionManager, responseTimeout time.Duration) *SessionManagerServer {
|
||||
return &SessionManagerServer{
|
||||
sessionManager: sessionManager,
|
||||
responseTimeout: responseTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SessionManagerServer) Serve(ctx context.Context, stream io.ReadWriteCloser) error {
|
||||
signature, err := determineProtocol(stream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch signature {
|
||||
case rpcStreamProtocolSignature:
|
||||
break
|
||||
case dataStreamProtocolSignature:
|
||||
return errDataStreamNotSupported
|
||||
default:
|
||||
return fmt.Errorf("unknown protocol %v", signature)
|
||||
}
|
||||
|
||||
// Every new quic.Stream request aligns to a new RPC request, this is why there is a timeout for the server-side
|
||||
// of the RPC request.
|
||||
ctx, cancel := context.WithTimeout(ctx, s.responseTimeout)
|
||||
defer cancel()
|
||||
|
||||
transport := tunnelrpc.SafeTransport(stream)
|
||||
defer transport.Close()
|
||||
|
||||
main := pogs.SessionManager_ServerToClient(s.sessionManager)
|
||||
rpcConn := rpc.NewConn(transport, rpc.MainInterface(main.Client))
|
||||
defer rpcConn.Close()
|
||||
|
||||
select {
|
||||
case <-rpcConn.Done():
|
||||
return rpcConn.Err()
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package tunnelrpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/metrics"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
type RegistrationClient interface {
|
||||
RegisterConnection(
|
||||
ctx context.Context,
|
||||
auth pogs.TunnelAuth,
|
||||
tunnelID uuid.UUID,
|
||||
options *pogs.ConnectionOptions,
|
||||
connIndex uint8,
|
||||
edgeAddress net.IP,
|
||||
) (*pogs.ConnectionDetails, error)
|
||||
SendLocalConfiguration(ctx context.Context, config []byte) error
|
||||
GracefulShutdown(ctx context.Context, gracePeriod time.Duration)
|
||||
Close()
|
||||
}
|
||||
|
||||
type registrationClient struct {
|
||||
client pogs.RegistrationServer_PogsClient
|
||||
transport rpc.Transport
|
||||
requestTimeout time.Duration
|
||||
}
|
||||
|
||||
func NewRegistrationClient(ctx context.Context, stream io.ReadWriteCloser, requestTimeout time.Duration) RegistrationClient {
|
||||
transport := SafeTransport(stream)
|
||||
conn := rpc.NewConn(transport)
|
||||
client := pogs.NewRegistrationServer_PogsClient(conn.Bootstrap(ctx), conn)
|
||||
return ®istrationClient{
|
||||
client: client,
|
||||
transport: transport,
|
||||
requestTimeout: requestTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *registrationClient) RegisterConnection(
|
||||
ctx context.Context,
|
||||
auth pogs.TunnelAuth,
|
||||
tunnelID uuid.UUID,
|
||||
options *pogs.ConnectionOptions,
|
||||
connIndex uint8,
|
||||
edgeAddress net.IP,
|
||||
) (*pogs.ConnectionDetails, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, r.requestTimeout)
|
||||
defer cancel()
|
||||
defer metrics.CapnpMetrics.ClientOperations.WithLabelValues(metrics.Registration, metrics.OperationRegisterConnection).Inc()
|
||||
timer := metrics.NewClientOperationLatencyObserver(metrics.Registration, metrics.OperationRegisterConnection)
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
conn, err := r.client.RegisterConnection(ctx, auth, tunnelID, connIndex, options)
|
||||
if err != nil {
|
||||
metrics.CapnpMetrics.ClientFailures.WithLabelValues(metrics.Registration, metrics.OperationRegisterConnection).Inc()
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (r *registrationClient) SendLocalConfiguration(ctx context.Context, config []byte) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, r.requestTimeout)
|
||||
defer cancel()
|
||||
defer metrics.CapnpMetrics.ClientOperations.WithLabelValues(metrics.Registration, metrics.OperationUpdateLocalConfiguration).Inc()
|
||||
timer := metrics.NewClientOperationLatencyObserver(metrics.Registration, metrics.OperationUpdateLocalConfiguration)
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
err := r.client.SendLocalConfiguration(ctx, config)
|
||||
if err != nil {
|
||||
metrics.CapnpMetrics.ClientFailures.WithLabelValues(metrics.Registration, metrics.OperationUpdateLocalConfiguration).Inc()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *registrationClient) GracefulShutdown(ctx context.Context, gracePeriod time.Duration) {
|
||||
ctx, cancel := context.WithTimeout(ctx, gracePeriod)
|
||||
defer cancel()
|
||||
defer metrics.CapnpMetrics.ClientOperations.WithLabelValues(metrics.Registration, metrics.OperationUnregisterConnection).Inc()
|
||||
timer := metrics.NewClientOperationLatencyObserver(metrics.Registration, metrics.OperationUnregisterConnection)
|
||||
defer timer.ObserveDuration()
|
||||
err := r.client.UnregisterConnection(ctx)
|
||||
if err != nil {
|
||||
metrics.CapnpMetrics.ClientFailures.WithLabelValues(metrics.Registration, metrics.OperationUnregisterConnection).Inc()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *registrationClient) Close() {
|
||||
// Closing the client will also close the connection
|
||||
_ = r.client.Close()
|
||||
// Closing the transport also closes the stream
|
||||
_ = r.transport.Close()
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package tunnelrpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
// RegistrationServer provides a handler interface for a client to provide methods to handle the different types of
|
||||
// requests that can be communicated by the stream.
|
||||
type RegistrationServer struct {
|
||||
registrationServer pogs.RegistrationServer
|
||||
}
|
||||
|
||||
func NewRegistrationServer(registrationServer pogs.RegistrationServer) *RegistrationServer {
|
||||
return &RegistrationServer{
|
||||
registrationServer: registrationServer,
|
||||
}
|
||||
}
|
||||
|
||||
// Serve listens for all RegistrationServer RPCs, including UnregisterConnection until the underlying connection
|
||||
// is terminated.
|
||||
func (s *RegistrationServer) Serve(ctx context.Context, stream io.ReadWriteCloser) error {
|
||||
transport := SafeTransport(stream)
|
||||
defer transport.Close()
|
||||
|
||||
main := pogs.RegistrationServer_ServerToClient(s.registrationServer)
|
||||
rpcConn := rpc.NewConn(transport, rpc.MainInterface(main.Client))
|
||||
defer rpcConn.Close()
|
||||
|
||||
select {
|
||||
case <-rpcConn.Done():
|
||||
return rpcConn.Wait()
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package tunnelrpc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
)
|
||||
|
||||
const (
|
||||
// These default values are here so that we give some time for the underlying connection/stream
|
||||
// to recover in the face of what we believe to be temporarily errors.
|
||||
// We don't want to be too aggressive, as the end result of giving a final error (non-temporary)
|
||||
// will result in the connection to be dropped.
|
||||
// In turn, the other side will probably reconnect, which will put again more pressure in the overall system.
|
||||
// So, the best solution is to give it some conservative time to recover.
|
||||
defaultSleepBetweenTemporaryError = 500 * time.Millisecond
|
||||
defaultMaxRetries = 3
|
||||
)
|
||||
|
||||
type readWriterSafeTemporaryErrorCloser struct {
|
||||
io.ReadWriteCloser
|
||||
|
||||
retries int
|
||||
sleepBetweenRetries time.Duration
|
||||
maxRetries int
|
||||
}
|
||||
|
||||
func (r *readWriterSafeTemporaryErrorCloser) Read(p []byte) (n int, err error) {
|
||||
n, err = r.ReadWriteCloser.Read(p)
|
||||
|
||||
// if there was a failure reading from the read closer, and the error is temporary, try again in some seconds
|
||||
// otherwise, just fail without a temporary error.
|
||||
if n == 0 && err != nil && isTemporaryError(err) {
|
||||
if r.retries >= r.maxRetries {
|
||||
return 0, errors.Wrap(err, "failed read from capnproto ReaderWriter after multiple temporary errors")
|
||||
} else {
|
||||
r.retries += 1
|
||||
|
||||
// sleep for some time to prevent quick read loops that cause exhaustion of CPU resources
|
||||
time.Sleep(r.sleepBetweenRetries)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
r.retries = 0
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func SafeTransport(rw io.ReadWriteCloser) rpc.Transport {
|
||||
return rpc.StreamTransport(&readWriterSafeTemporaryErrorCloser{
|
||||
ReadWriteCloser: rw,
|
||||
maxRetries: defaultMaxRetries,
|
||||
sleepBetweenRetries: defaultSleepBetweenTemporaryError,
|
||||
})
|
||||
}
|
||||
|
||||
// isTemporaryError reports whether e has a Temporary() method that
|
||||
// returns true.
|
||||
func isTemporaryError(e error) bool {
|
||||
type temp interface {
|
||||
Temporary() bool
|
||||
}
|
||||
t, ok := e.(temp)
|
||||
return ok && t.Temporary()
|
||||
}
|
||||
+40
-27
@@ -91,11 +91,12 @@ logr design but also left out some parts and changed others:
|
||||
| Adding a name to a logger | `WithName` | no API |
|
||||
| Modify verbosity of log entries in a call chain | `V` | no API |
|
||||
| Grouping of key/value pairs | not supported | `WithGroup`, `GroupValue` |
|
||||
| Pass context for extracting additional values | no API | API variants like `InfoCtx` |
|
||||
|
||||
The high-level slog API is explicitly meant to be one of many different APIs
|
||||
that can be layered on top of a shared `slog.Handler`. logr is one such
|
||||
alternative API, with [interoperability](#slog-interoperability) provided by the [`slogr`](slogr)
|
||||
package.
|
||||
alternative API, with [interoperability](#slog-interoperability) provided by
|
||||
some conversion functions.
|
||||
|
||||
### Inspiration
|
||||
|
||||
@@ -145,24 +146,24 @@ There are implementations for the following logging libraries:
|
||||
## slog interoperability
|
||||
|
||||
Interoperability goes both ways, using the `logr.Logger` API with a `slog.Handler`
|
||||
and using the `slog.Logger` API with a `logr.LogSink`. [slogr](./slogr) provides `NewLogr` and
|
||||
`NewSlogHandler` API calls to convert between a `logr.Logger` and a `slog.Handler`.
|
||||
and using the `slog.Logger` API with a `logr.LogSink`. `FromSlogHandler` and
|
||||
`ToSlogHandler` convert between a `logr.Logger` and a `slog.Handler`.
|
||||
As usual, `slog.New` can be used to wrap such a `slog.Handler` in the high-level
|
||||
slog API. `slogr` itself leaves that to the caller.
|
||||
slog API.
|
||||
|
||||
## Using a `logr.Sink` as backend for slog
|
||||
### Using a `logr.LogSink` as backend for slog
|
||||
|
||||
Ideally, a logr sink implementation should support both logr and slog by
|
||||
implementing both the normal logr interface(s) and `slogr.SlogSink`. Because
|
||||
implementing both the normal logr interface(s) and `SlogSink`. Because
|
||||
of a conflict in the parameters of the common `Enabled` method, it is [not
|
||||
possible to implement both slog.Handler and logr.Sink in the same
|
||||
type](https://github.com/golang/go/issues/59110).
|
||||
|
||||
If both are supported, log calls can go from the high-level APIs to the backend
|
||||
without the need to convert parameters. `NewLogr` and `NewSlogHandler` can
|
||||
without the need to convert parameters. `FromSlogHandler` and `ToSlogHandler` can
|
||||
convert back and forth without adding additional wrappers, with one exception:
|
||||
when `Logger.V` was used to adjust the verbosity for a `slog.Handler`, then
|
||||
`NewSlogHandler` has to use a wrapper which adjusts the verbosity for future
|
||||
`ToSlogHandler` has to use a wrapper which adjusts the verbosity for future
|
||||
log calls.
|
||||
|
||||
Such an implementation should also support values that implement specific
|
||||
@@ -187,13 +188,13 @@ Not supporting slog has several drawbacks:
|
||||
These drawbacks are severe enough that applications using a mixture of slog and
|
||||
logr should switch to a different backend.
|
||||
|
||||
## Using a `slog.Handler` as backend for logr
|
||||
### Using a `slog.Handler` as backend for logr
|
||||
|
||||
Using a plain `slog.Handler` without support for logr works better than the
|
||||
other direction:
|
||||
- All logr verbosity levels can be mapped 1:1 to their corresponding slog level
|
||||
by negating them.
|
||||
- Stack unwinding is done by the `slogr.SlogSink` and the resulting program
|
||||
- Stack unwinding is done by the `SlogSink` and the resulting program
|
||||
counter is passed to the `slog.Handler`.
|
||||
- Names added via `Logger.WithName` are gathered and recorded in an additional
|
||||
attribute with `logger` as key and the names separated by slash as value.
|
||||
@@ -205,27 +206,39 @@ ideally support both `logr.Marshaler` and `slog.Valuer`. If compatibility
|
||||
with logr implementations without slog support is not important, then
|
||||
`slog.Valuer` is sufficient.
|
||||
|
||||
## Context support for slog
|
||||
### Context support for slog
|
||||
|
||||
Storing a logger in a `context.Context` is not supported by
|
||||
slog. `logr.NewContext` and `logr.FromContext` can be used with slog like this
|
||||
to fill this gap:
|
||||
slog. `NewContextWithSlogLogger` and `FromContextAsSlogLogger` can be
|
||||
used to fill this gap. They store and retrieve a `slog.Logger` pointer
|
||||
under the same context key that is also used by `NewContext` and
|
||||
`FromContext` for `logr.Logger` value.
|
||||
|
||||
func HandlerFromContext(ctx context.Context) slog.Handler {
|
||||
logger, err := logr.FromContext(ctx)
|
||||
if err == nil {
|
||||
return slogr.NewSlogHandler(logger)
|
||||
}
|
||||
return slog.Default().Handler()
|
||||
}
|
||||
When `NewContextWithSlogLogger` is followed by `FromContext`, the latter will
|
||||
automatically convert the `slog.Logger` to a
|
||||
`logr.Logger`. `FromContextAsSlogLogger` does the same for the other direction.
|
||||
|
||||
func ContextWithHandler(ctx context.Context, handler slog.Handler) context.Context {
|
||||
return logr.NewContext(ctx, slogr.NewLogr(handler))
|
||||
}
|
||||
With this approach, binaries which use either slog or logr are as efficient as
|
||||
possible with no unnecessary allocations. This is also why the API stores a
|
||||
`slog.Logger` pointer: when storing a `slog.Handler`, creating a `slog.Logger`
|
||||
on retrieval would need to allocate one.
|
||||
|
||||
The downside is that storing and retrieving a `slog.Handler` needs more
|
||||
allocations compared to using a `logr.Logger`. Therefore the recommendation is
|
||||
to use the `logr.Logger` API in code which uses contextual logging.
|
||||
The downside is that switching back and forth needs more allocations. Because
|
||||
logr is the API that is already in use by different packages, in particular
|
||||
Kubernetes, the recommendation is to use the `logr.Logger` API in code which
|
||||
uses contextual logging.
|
||||
|
||||
An alternative to adding values to a logger and storing that logger in the
|
||||
context is to store the values in the context and to configure a logging
|
||||
backend to extract those values when emitting log entries. This only works when
|
||||
log calls are passed the context, which is not supported by the logr API.
|
||||
|
||||
With the slog API, it is possible, but not
|
||||
required. https://github.com/veqryn/slog-context is a package for slog which
|
||||
provides additional support code for this approach. It also contains wrappers
|
||||
for the context functions in logr, so developers who prefer to not use the logr
|
||||
APIs directly can use those instead and the resulting code will still be
|
||||
interoperable with logr.
|
||||
|
||||
## FAQ
|
||||
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright 2023 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
// contextKey is how we find Loggers in a context.Context. With Go < 1.21,
|
||||
// the value is always a Logger value. With Go >= 1.21, the value can be a
|
||||
// Logger value or a slog.Logger pointer.
|
||||
type contextKey struct{}
|
||||
|
||||
// notFoundError exists to carry an IsNotFound method.
|
||||
type notFoundError struct{}
|
||||
|
||||
func (notFoundError) Error() string {
|
||||
return "no logr.Logger was present"
|
||||
}
|
||||
|
||||
func (notFoundError) IsNotFound() bool {
|
||||
return true
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
//go:build !go1.21
|
||||
// +build !go1.21
|
||||
|
||||
/*
|
||||
Copyright 2019 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// FromContext returns a Logger from ctx or an error if no Logger is found.
|
||||
func FromContext(ctx context.Context) (Logger, error) {
|
||||
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return Logger{}, notFoundError{}
|
||||
}
|
||||
|
||||
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
|
||||
// returns a Logger that discards all log messages.
|
||||
func FromContextOrDiscard(ctx context.Context) Logger {
|
||||
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return Discard()
|
||||
}
|
||||
|
||||
// NewContext returns a new Context, derived from ctx, which carries the
|
||||
// provided Logger.
|
||||
func NewContext(ctx context.Context, logger Logger) context.Context {
|
||||
return context.WithValue(ctx, contextKey{}, logger)
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/*
|
||||
Copyright 2019 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// FromContext returns a Logger from ctx or an error if no Logger is found.
|
||||
func FromContext(ctx context.Context) (Logger, error) {
|
||||
v := ctx.Value(contextKey{})
|
||||
if v == nil {
|
||||
return Logger{}, notFoundError{}
|
||||
}
|
||||
|
||||
switch v := v.(type) {
|
||||
case Logger:
|
||||
return v, nil
|
||||
case *slog.Logger:
|
||||
return FromSlogHandler(v.Handler()), nil
|
||||
default:
|
||||
// Not reached.
|
||||
panic(fmt.Sprintf("unexpected value type for logr context key: %T", v))
|
||||
}
|
||||
}
|
||||
|
||||
// FromContextAsSlogLogger returns a slog.Logger from ctx or nil if no such Logger is found.
|
||||
func FromContextAsSlogLogger(ctx context.Context) *slog.Logger {
|
||||
v := ctx.Value(contextKey{})
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := v.(type) {
|
||||
case Logger:
|
||||
return slog.New(ToSlogHandler(v))
|
||||
case *slog.Logger:
|
||||
return v
|
||||
default:
|
||||
// Not reached.
|
||||
panic(fmt.Sprintf("unexpected value type for logr context key: %T", v))
|
||||
}
|
||||
}
|
||||
|
||||
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
|
||||
// returns a Logger that discards all log messages.
|
||||
func FromContextOrDiscard(ctx context.Context) Logger {
|
||||
if logger, err := FromContext(ctx); err == nil {
|
||||
return logger
|
||||
}
|
||||
return Discard()
|
||||
}
|
||||
|
||||
// NewContext returns a new Context, derived from ctx, which carries the
|
||||
// provided Logger.
|
||||
func NewContext(ctx context.Context, logger Logger) context.Context {
|
||||
return context.WithValue(ctx, contextKey{}, logger)
|
||||
}
|
||||
|
||||
// NewContextWithSlogLogger returns a new Context, derived from ctx, which carries the
|
||||
// provided slog.Logger.
|
||||
func NewContextWithSlogLogger(ctx context.Context, logger *slog.Logger) context.Context {
|
||||
return context.WithValue(ctx, contextKey{}, logger)
|
||||
}
|
||||
+148
-41
@@ -100,6 +100,11 @@ type Options struct {
|
||||
// details, see docs for Go's time.Layout.
|
||||
TimestampFormat string
|
||||
|
||||
// LogInfoLevel tells funcr what key to use to log the info level.
|
||||
// If not specified, the info level will be logged as "level".
|
||||
// If this is set to "", the info level will not be logged at all.
|
||||
LogInfoLevel *string
|
||||
|
||||
// Verbosity tells funcr which V logs to produce. Higher values enable
|
||||
// more logs. Info logs at or below this level will be written, while logs
|
||||
// above this level will be discarded.
|
||||
@@ -213,6 +218,10 @@ func newFormatter(opts Options, outfmt outputFormat) Formatter {
|
||||
if opts.MaxLogDepth == 0 {
|
||||
opts.MaxLogDepth = defaultMaxLogDepth
|
||||
}
|
||||
if opts.LogInfoLevel == nil {
|
||||
opts.LogInfoLevel = new(string)
|
||||
*opts.LogInfoLevel = "level"
|
||||
}
|
||||
f := Formatter{
|
||||
outputFormat: outfmt,
|
||||
prefix: "",
|
||||
@@ -227,12 +236,15 @@ func newFormatter(opts Options, outfmt outputFormat) Formatter {
|
||||
// implementation. It should be constructed with NewFormatter. Some of
|
||||
// its methods directly implement logr.LogSink.
|
||||
type Formatter struct {
|
||||
outputFormat outputFormat
|
||||
prefix string
|
||||
values []any
|
||||
valuesStr string
|
||||
depth int
|
||||
opts *Options
|
||||
outputFormat outputFormat
|
||||
prefix string
|
||||
values []any
|
||||
valuesStr string
|
||||
parentValuesStr string
|
||||
depth int
|
||||
opts *Options
|
||||
group string // for slog groups
|
||||
groupDepth int
|
||||
}
|
||||
|
||||
// outputFormat indicates which outputFormat to use.
|
||||
@@ -253,33 +265,62 @@ func (f Formatter) render(builtins, args []any) string {
|
||||
// Empirically bytes.Buffer is faster than strings.Builder for this.
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
if f.outputFormat == outputJSON {
|
||||
buf.WriteByte('{')
|
||||
buf.WriteByte('{') // for the whole line
|
||||
}
|
||||
|
||||
vals := builtins
|
||||
if hook := f.opts.RenderBuiltinsHook; hook != nil {
|
||||
vals = hook(f.sanitize(vals))
|
||||
}
|
||||
f.flatten(buf, vals, false, false) // keys are ours, no need to escape
|
||||
continuing := len(builtins) > 0
|
||||
if len(f.valuesStr) > 0 {
|
||||
|
||||
if f.parentValuesStr != "" {
|
||||
if continuing {
|
||||
if f.outputFormat == outputJSON {
|
||||
buf.WriteByte(',')
|
||||
} else {
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
buf.WriteString(f.parentValuesStr)
|
||||
continuing = true
|
||||
buf.WriteString(f.valuesStr)
|
||||
}
|
||||
|
||||
groupDepth := f.groupDepth
|
||||
if f.group != "" {
|
||||
if f.valuesStr != "" || len(args) != 0 {
|
||||
if continuing {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteByte('{') // for the group
|
||||
continuing = false
|
||||
} else {
|
||||
// The group was empty
|
||||
groupDepth--
|
||||
}
|
||||
}
|
||||
|
||||
if f.valuesStr != "" {
|
||||
if continuing {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
buf.WriteString(f.valuesStr)
|
||||
continuing = true
|
||||
}
|
||||
|
||||
vals = args
|
||||
if hook := f.opts.RenderArgsHook; hook != nil {
|
||||
vals = hook(f.sanitize(vals))
|
||||
}
|
||||
f.flatten(buf, vals, continuing, true) // escape user-provided keys
|
||||
if f.outputFormat == outputJSON {
|
||||
buf.WriteByte('}')
|
||||
|
||||
for i := 0; i < groupDepth; i++ {
|
||||
buf.WriteByte('}') // for the groups
|
||||
}
|
||||
|
||||
if f.outputFormat == outputJSON {
|
||||
buf.WriteByte('}') // for the whole line
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
@@ -298,9 +339,16 @@ func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, esc
|
||||
if len(kvList)%2 != 0 {
|
||||
kvList = append(kvList, noValue)
|
||||
}
|
||||
copied := false
|
||||
for i := 0; i < len(kvList); i += 2 {
|
||||
k, ok := kvList[i].(string)
|
||||
if !ok {
|
||||
if !copied {
|
||||
newList := make([]any, len(kvList))
|
||||
copy(newList, kvList)
|
||||
kvList = newList
|
||||
copied = true
|
||||
}
|
||||
k = f.nonStringKey(kvList[i])
|
||||
kvList[i] = k
|
||||
}
|
||||
@@ -308,7 +356,7 @@ func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, esc
|
||||
|
||||
if i > 0 || continuing {
|
||||
if f.outputFormat == outputJSON {
|
||||
buf.WriteByte(',')
|
||||
buf.WriteByte(f.comma())
|
||||
} else {
|
||||
// In theory the format could be something we don't understand. In
|
||||
// practice, we control it, so it won't be.
|
||||
@@ -316,24 +364,35 @@ func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, esc
|
||||
}
|
||||
}
|
||||
|
||||
if escapeKeys {
|
||||
buf.WriteString(prettyString(k))
|
||||
} else {
|
||||
// this is faster
|
||||
buf.WriteByte('"')
|
||||
buf.WriteString(k)
|
||||
buf.WriteByte('"')
|
||||
}
|
||||
if f.outputFormat == outputJSON {
|
||||
buf.WriteByte(':')
|
||||
} else {
|
||||
buf.WriteByte('=')
|
||||
}
|
||||
buf.WriteString(f.quoted(k, escapeKeys))
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteString(f.pretty(v))
|
||||
}
|
||||
return kvList
|
||||
}
|
||||
|
||||
func (f Formatter) quoted(str string, escape bool) string {
|
||||
if escape {
|
||||
return prettyString(str)
|
||||
}
|
||||
// this is faster
|
||||
return `"` + str + `"`
|
||||
}
|
||||
|
||||
func (f Formatter) comma() byte {
|
||||
if f.outputFormat == outputJSON {
|
||||
return ','
|
||||
}
|
||||
return ' '
|
||||
}
|
||||
|
||||
func (f Formatter) colon() byte {
|
||||
if f.outputFormat == outputJSON {
|
||||
return ':'
|
||||
}
|
||||
return '='
|
||||
}
|
||||
|
||||
func (f Formatter) pretty(value any) string {
|
||||
return f.prettyWithFlags(value, 0, 0)
|
||||
}
|
||||
@@ -407,12 +466,12 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
|
||||
}
|
||||
for i := 0; i < len(v); i += 2 {
|
||||
if i > 0 {
|
||||
buf.WriteByte(',')
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
k, _ := v[i].(string) // sanitize() above means no need to check success
|
||||
// arbitrary keys might need escaping
|
||||
buf.WriteString(prettyString(k))
|
||||
buf.WriteByte(':')
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1))
|
||||
}
|
||||
if flags&flagRawStruct == 0 {
|
||||
@@ -481,7 +540,7 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
|
||||
continue
|
||||
}
|
||||
if printComma {
|
||||
buf.WriteByte(',')
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
printComma = true // if we got here, we are rendering a field
|
||||
if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" {
|
||||
@@ -492,10 +551,8 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
|
||||
name = fld.Name
|
||||
}
|
||||
// field names can't contain characters which need escaping
|
||||
buf.WriteByte('"')
|
||||
buf.WriteString(name)
|
||||
buf.WriteByte('"')
|
||||
buf.WriteByte(':')
|
||||
buf.WriteString(f.quoted(name, false))
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1))
|
||||
}
|
||||
if flags&flagRawStruct == 0 {
|
||||
@@ -520,7 +577,7 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
|
||||
buf.WriteByte('[')
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if i > 0 {
|
||||
buf.WriteByte(',')
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
e := v.Index(i)
|
||||
buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1))
|
||||
@@ -534,7 +591,7 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
|
||||
i := 0
|
||||
for it.Next() {
|
||||
if i > 0 {
|
||||
buf.WriteByte(',')
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
// If a map key supports TextMarshaler, use it.
|
||||
keystr := ""
|
||||
@@ -556,7 +613,7 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
|
||||
}
|
||||
}
|
||||
buf.WriteString(keystr)
|
||||
buf.WriteByte(':')
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1))
|
||||
i++
|
||||
}
|
||||
@@ -706,6 +763,53 @@ func (f Formatter) sanitize(kvList []any) []any {
|
||||
return kvList
|
||||
}
|
||||
|
||||
// startGroup opens a new group scope (basically a sub-struct), which locks all
|
||||
// the current saved values and starts them anew. This is needed to satisfy
|
||||
// slog.
|
||||
func (f *Formatter) startGroup(group string) {
|
||||
// Unnamed groups are just inlined.
|
||||
if group == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Any saved values can no longer be changed.
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
continuing := false
|
||||
|
||||
if f.parentValuesStr != "" {
|
||||
buf.WriteString(f.parentValuesStr)
|
||||
continuing = true
|
||||
}
|
||||
|
||||
if f.group != "" && f.valuesStr != "" {
|
||||
if continuing {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteByte('{') // for the group
|
||||
continuing = false
|
||||
}
|
||||
|
||||
if f.valuesStr != "" {
|
||||
if continuing {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
buf.WriteString(f.valuesStr)
|
||||
}
|
||||
|
||||
// NOTE: We don't close the scope here - that's done later, when a log line
|
||||
// is actually rendered (because we have N scopes to close).
|
||||
|
||||
f.parentValuesStr = buf.String()
|
||||
|
||||
// Start collecting new values.
|
||||
f.group = group
|
||||
f.groupDepth++
|
||||
f.valuesStr = ""
|
||||
f.values = nil
|
||||
}
|
||||
|
||||
// Init configures this Formatter from runtime info, such as the call depth
|
||||
// imposed by logr itself.
|
||||
// Note that this receiver is a pointer, so depth can be saved.
|
||||
@@ -740,7 +844,10 @@ func (f Formatter) FormatInfo(level int, msg string, kvList []any) (prefix, args
|
||||
if policy := f.opts.LogCaller; policy == All || policy == Info {
|
||||
args = append(args, "caller", f.caller())
|
||||
}
|
||||
args = append(args, "level", level, "msg", msg)
|
||||
if key := *f.opts.LogInfoLevel; key != "" {
|
||||
args = append(args, key, level)
|
||||
}
|
||||
args = append(args, "msg", msg)
|
||||
return prefix, f.render(args, kvList)
|
||||
}
|
||||
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/*
|
||||
Copyright 2023 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package funcr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
var _ logr.SlogSink = &fnlogger{}
|
||||
|
||||
const extraSlogSinkDepth = 3 // 2 for slog, 1 for SlogSink
|
||||
|
||||
func (l fnlogger) Handle(_ context.Context, record slog.Record) error {
|
||||
kvList := make([]any, 0, 2*record.NumAttrs())
|
||||
record.Attrs(func(attr slog.Attr) bool {
|
||||
kvList = attrToKVs(attr, kvList)
|
||||
return true
|
||||
})
|
||||
|
||||
if record.Level >= slog.LevelError {
|
||||
l.WithCallDepth(extraSlogSinkDepth).Error(nil, record.Message, kvList...)
|
||||
} else {
|
||||
level := l.levelFromSlog(record.Level)
|
||||
l.WithCallDepth(extraSlogSinkDepth).Info(level, record.Message, kvList...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l fnlogger) WithAttrs(attrs []slog.Attr) logr.SlogSink {
|
||||
kvList := make([]any, 0, 2*len(attrs))
|
||||
for _, attr := range attrs {
|
||||
kvList = attrToKVs(attr, kvList)
|
||||
}
|
||||
l.AddValues(kvList)
|
||||
return &l
|
||||
}
|
||||
|
||||
func (l fnlogger) WithGroup(name string) logr.SlogSink {
|
||||
l.startGroup(name)
|
||||
return &l
|
||||
}
|
||||
|
||||
// attrToKVs appends a slog.Attr to a logr-style kvList. It handle slog Groups
|
||||
// and other details of slog.
|
||||
func attrToKVs(attr slog.Attr, kvList []any) []any {
|
||||
attrVal := attr.Value.Resolve()
|
||||
if attrVal.Kind() == slog.KindGroup {
|
||||
groupVal := attrVal.Group()
|
||||
grpKVs := make([]any, 0, 2*len(groupVal))
|
||||
for _, attr := range groupVal {
|
||||
grpKVs = attrToKVs(attr, grpKVs)
|
||||
}
|
||||
if attr.Key == "" {
|
||||
// slog says we have to inline these
|
||||
kvList = append(kvList, grpKVs...)
|
||||
} else {
|
||||
kvList = append(kvList, attr.Key, PseudoStruct(grpKVs))
|
||||
}
|
||||
} else if attr.Key != "" {
|
||||
kvList = append(kvList, attr.Key, attrVal.Any())
|
||||
}
|
||||
|
||||
return kvList
|
||||
}
|
||||
|
||||
// levelFromSlog adjusts the level by the logger's verbosity and negates it.
|
||||
// It ensures that the result is >= 0. This is necessary because the result is
|
||||
// passed to a LogSink and that API did not historically document whether
|
||||
// levels could be negative or what that meant.
|
||||
//
|
||||
// Some example usage:
|
||||
//
|
||||
// logrV0 := getMyLogger()
|
||||
// logrV2 := logrV0.V(2)
|
||||
// slogV2 := slog.New(logr.ToSlogHandler(logrV2))
|
||||
// slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
|
||||
// slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2)
|
||||
// slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0)
|
||||
func (l fnlogger) levelFromSlog(level slog.Level) int {
|
||||
result := -level
|
||||
if result < 0 {
|
||||
result = 0 // because LogSink doesn't expect negative V levels
|
||||
}
|
||||
return int(result)
|
||||
}
|
||||
-43
@@ -207,10 +207,6 @@ limitations under the License.
|
||||
// those.
|
||||
package logr
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// New returns a new Logger instance. This is primarily used by libraries
|
||||
// implementing LogSink, rather than end users. Passing a nil sink will create
|
||||
// a Logger which discards all log lines.
|
||||
@@ -410,45 +406,6 @@ func (l Logger) IsZero() bool {
|
||||
return l.sink == nil
|
||||
}
|
||||
|
||||
// contextKey is how we find Loggers in a context.Context.
|
||||
type contextKey struct{}
|
||||
|
||||
// FromContext returns a Logger from ctx or an error if no Logger is found.
|
||||
func FromContext(ctx context.Context) (Logger, error) {
|
||||
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return Logger{}, notFoundError{}
|
||||
}
|
||||
|
||||
// notFoundError exists to carry an IsNotFound method.
|
||||
type notFoundError struct{}
|
||||
|
||||
func (notFoundError) Error() string {
|
||||
return "no logr.Logger was present"
|
||||
}
|
||||
|
||||
func (notFoundError) IsNotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
|
||||
// returns a Logger that discards all log messages.
|
||||
func FromContextOrDiscard(ctx context.Context) Logger {
|
||||
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return Discard()
|
||||
}
|
||||
|
||||
// NewContext returns a new Context, derived from ctx, which carries the
|
||||
// provided Logger.
|
||||
func NewContext(ctx context.Context, logger Logger) context.Context {
|
||||
return context.WithValue(ctx, contextKey{}, logger)
|
||||
}
|
||||
|
||||
// RuntimeInfo holds information that the logr "core" library knows which
|
||||
// LogSinks might want to know.
|
||||
type RuntimeInfo struct {
|
||||
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/*
|
||||
Copyright 2023 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
type slogHandler struct {
|
||||
// May be nil, in which case all logs get discarded.
|
||||
sink LogSink
|
||||
// Non-nil if sink is non-nil and implements SlogSink.
|
||||
slogSink SlogSink
|
||||
|
||||
// groupPrefix collects values from WithGroup calls. It gets added as
|
||||
// prefix to value keys when handling a log record.
|
||||
groupPrefix string
|
||||
|
||||
// levelBias can be set when constructing the handler to influence the
|
||||
// slog.Level of log records. A positive levelBias reduces the
|
||||
// slog.Level value. slog has no API to influence this value after the
|
||||
// handler got created, so it can only be set indirectly through
|
||||
// Logger.V.
|
||||
levelBias slog.Level
|
||||
}
|
||||
|
||||
var _ slog.Handler = &slogHandler{}
|
||||
|
||||
// groupSeparator is used to concatenate WithGroup names and attribute keys.
|
||||
const groupSeparator = "."
|
||||
|
||||
// GetLevel is used for black box unit testing.
|
||||
func (l *slogHandler) GetLevel() slog.Level {
|
||||
return l.levelBias
|
||||
}
|
||||
|
||||
func (l *slogHandler) Enabled(_ context.Context, level slog.Level) bool {
|
||||
return l.sink != nil && (level >= slog.LevelError || l.sink.Enabled(l.levelFromSlog(level)))
|
||||
}
|
||||
|
||||
func (l *slogHandler) Handle(ctx context.Context, record slog.Record) error {
|
||||
if l.slogSink != nil {
|
||||
// Only adjust verbosity level of log entries < slog.LevelError.
|
||||
if record.Level < slog.LevelError {
|
||||
record.Level -= l.levelBias
|
||||
}
|
||||
return l.slogSink.Handle(ctx, record)
|
||||
}
|
||||
|
||||
// No need to check for nil sink here because Handle will only be called
|
||||
// when Enabled returned true.
|
||||
|
||||
kvList := make([]any, 0, 2*record.NumAttrs())
|
||||
record.Attrs(func(attr slog.Attr) bool {
|
||||
kvList = attrToKVs(attr, l.groupPrefix, kvList)
|
||||
return true
|
||||
})
|
||||
if record.Level >= slog.LevelError {
|
||||
l.sinkWithCallDepth().Error(nil, record.Message, kvList...)
|
||||
} else {
|
||||
level := l.levelFromSlog(record.Level)
|
||||
l.sinkWithCallDepth().Info(level, record.Message, kvList...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sinkWithCallDepth adjusts the stack unwinding so that when Error or Info
|
||||
// are called by Handle, code in slog gets skipped.
|
||||
//
|
||||
// This offset currently (Go 1.21.0) works for calls through
|
||||
// slog.New(ToSlogHandler(...)). There's no guarantee that the call
|
||||
// chain won't change. Wrapping the handler will also break unwinding. It's
|
||||
// still better than not adjusting at all....
|
||||
//
|
||||
// This cannot be done when constructing the handler because FromSlogHandler needs
|
||||
// access to the original sink without this adjustment. A second copy would
|
||||
// work, but then WithAttrs would have to be called for both of them.
|
||||
func (l *slogHandler) sinkWithCallDepth() LogSink {
|
||||
if sink, ok := l.sink.(CallDepthLogSink); ok {
|
||||
return sink.WithCallDepth(2)
|
||||
}
|
||||
return l.sink
|
||||
}
|
||||
|
||||
func (l *slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
if l.sink == nil || len(attrs) == 0 {
|
||||
return l
|
||||
}
|
||||
|
||||
clone := *l
|
||||
if l.slogSink != nil {
|
||||
clone.slogSink = l.slogSink.WithAttrs(attrs)
|
||||
clone.sink = clone.slogSink
|
||||
} else {
|
||||
kvList := make([]any, 0, 2*len(attrs))
|
||||
for _, attr := range attrs {
|
||||
kvList = attrToKVs(attr, l.groupPrefix, kvList)
|
||||
}
|
||||
clone.sink = l.sink.WithValues(kvList...)
|
||||
}
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (l *slogHandler) WithGroup(name string) slog.Handler {
|
||||
if l.sink == nil {
|
||||
return l
|
||||
}
|
||||
if name == "" {
|
||||
// slog says to inline empty groups
|
||||
return l
|
||||
}
|
||||
clone := *l
|
||||
if l.slogSink != nil {
|
||||
clone.slogSink = l.slogSink.WithGroup(name)
|
||||
clone.sink = clone.slogSink
|
||||
} else {
|
||||
clone.groupPrefix = addPrefix(clone.groupPrefix, name)
|
||||
}
|
||||
return &clone
|
||||
}
|
||||
|
||||
// attrToKVs appends a slog.Attr to a logr-style kvList. It handle slog Groups
|
||||
// and other details of slog.
|
||||
func attrToKVs(attr slog.Attr, groupPrefix string, kvList []any) []any {
|
||||
attrVal := attr.Value.Resolve()
|
||||
if attrVal.Kind() == slog.KindGroup {
|
||||
groupVal := attrVal.Group()
|
||||
grpKVs := make([]any, 0, 2*len(groupVal))
|
||||
prefix := groupPrefix
|
||||
if attr.Key != "" {
|
||||
prefix = addPrefix(groupPrefix, attr.Key)
|
||||
}
|
||||
for _, attr := range groupVal {
|
||||
grpKVs = attrToKVs(attr, prefix, grpKVs)
|
||||
}
|
||||
kvList = append(kvList, grpKVs...)
|
||||
} else if attr.Key != "" {
|
||||
kvList = append(kvList, addPrefix(groupPrefix, attr.Key), attrVal.Any())
|
||||
}
|
||||
|
||||
return kvList
|
||||
}
|
||||
|
||||
func addPrefix(prefix, name string) string {
|
||||
if prefix == "" {
|
||||
return name
|
||||
}
|
||||
if name == "" {
|
||||
return prefix
|
||||
}
|
||||
return prefix + groupSeparator + name
|
||||
}
|
||||
|
||||
// levelFromSlog adjusts the level by the logger's verbosity and negates it.
|
||||
// It ensures that the result is >= 0. This is necessary because the result is
|
||||
// passed to a LogSink and that API did not historically document whether
|
||||
// levels could be negative or what that meant.
|
||||
//
|
||||
// Some example usage:
|
||||
//
|
||||
// logrV0 := getMyLogger()
|
||||
// logrV2 := logrV0.V(2)
|
||||
// slogV2 := slog.New(logr.ToSlogHandler(logrV2))
|
||||
// slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
|
||||
// slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2)
|
||||
// slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0)
|
||||
func (l *slogHandler) levelFromSlog(level slog.Level) int {
|
||||
result := -level
|
||||
result += l.levelBias // in case the original Logger had a V level
|
||||
if result < 0 {
|
||||
result = 0 // because LogSink doesn't expect negative V levels
|
||||
}
|
||||
return int(result)
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/*
|
||||
Copyright 2023 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// FromSlogHandler returns a Logger which writes to the slog.Handler.
|
||||
//
|
||||
// The logr verbosity level is mapped to slog levels such that V(0) becomes
|
||||
// slog.LevelInfo and V(4) becomes slog.LevelDebug.
|
||||
func FromSlogHandler(handler slog.Handler) Logger {
|
||||
if handler, ok := handler.(*slogHandler); ok {
|
||||
if handler.sink == nil {
|
||||
return Discard()
|
||||
}
|
||||
return New(handler.sink).V(int(handler.levelBias))
|
||||
}
|
||||
return New(&slogSink{handler: handler})
|
||||
}
|
||||
|
||||
// ToSlogHandler returns a slog.Handler which writes to the same sink as the Logger.
|
||||
//
|
||||
// The returned logger writes all records with level >= slog.LevelError as
|
||||
// error log entries with LogSink.Error, regardless of the verbosity level of
|
||||
// the Logger:
|
||||
//
|
||||
// logger := <some Logger with 0 as verbosity level>
|
||||
// slog.New(ToSlogHandler(logger.V(10))).Error(...) -> logSink.Error(...)
|
||||
//
|
||||
// The level of all other records gets reduced by the verbosity
|
||||
// level of the Logger and the result is negated. If it happens
|
||||
// to be negative, then it gets replaced by zero because a LogSink
|
||||
// is not expected to handled negative levels:
|
||||
//
|
||||
// slog.New(ToSlogHandler(logger)).Debug(...) -> logger.GetSink().Info(level=4, ...)
|
||||
// slog.New(ToSlogHandler(logger)).Warning(...) -> logger.GetSink().Info(level=0, ...)
|
||||
// slog.New(ToSlogHandler(logger)).Info(...) -> logger.GetSink().Info(level=0, ...)
|
||||
// slog.New(ToSlogHandler(logger.V(4))).Info(...) -> logger.GetSink().Info(level=4, ...)
|
||||
func ToSlogHandler(logger Logger) slog.Handler {
|
||||
if sink, ok := logger.GetSink().(*slogSink); ok && logger.GetV() == 0 {
|
||||
return sink.handler
|
||||
}
|
||||
|
||||
handler := &slogHandler{sink: logger.GetSink(), levelBias: slog.Level(logger.GetV())}
|
||||
if slogSink, ok := handler.sink.(SlogSink); ok {
|
||||
handler.slogSink = slogSink
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
// SlogSink is an optional interface that a LogSink can implement to support
|
||||
// logging through the slog.Logger or slog.Handler APIs better. It then should
|
||||
// also support special slog values like slog.Group. When used as a
|
||||
// slog.Handler, the advantages are:
|
||||
//
|
||||
// - stack unwinding gets avoided in favor of logging the pre-recorded PC,
|
||||
// as intended by slog
|
||||
// - proper grouping of key/value pairs via WithGroup
|
||||
// - verbosity levels > slog.LevelInfo can be recorded
|
||||
// - less overhead
|
||||
//
|
||||
// Both APIs (Logger and slog.Logger/Handler) then are supported equally
|
||||
// well. Developers can pick whatever API suits them better and/or mix
|
||||
// packages which use either API in the same binary with a common logging
|
||||
// implementation.
|
||||
//
|
||||
// This interface is necessary because the type implementing the LogSink
|
||||
// interface cannot also implement the slog.Handler interface due to the
|
||||
// different prototype of the common Enabled method.
|
||||
//
|
||||
// An implementation could support both interfaces in two different types, but then
|
||||
// additional interfaces would be needed to convert between those types in FromSlogHandler
|
||||
// and ToSlogHandler.
|
||||
type SlogSink interface {
|
||||
LogSink
|
||||
|
||||
Handle(ctx context.Context, record slog.Record) error
|
||||
WithAttrs(attrs []slog.Attr) SlogSink
|
||||
WithGroup(name string) SlogSink
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/*
|
||||
Copyright 2023 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
_ LogSink = &slogSink{}
|
||||
_ CallDepthLogSink = &slogSink{}
|
||||
_ Underlier = &slogSink{}
|
||||
)
|
||||
|
||||
// Underlier is implemented by the LogSink returned by NewFromLogHandler.
|
||||
type Underlier interface {
|
||||
// GetUnderlying returns the Handler used by the LogSink.
|
||||
GetUnderlying() slog.Handler
|
||||
}
|
||||
|
||||
const (
|
||||
// nameKey is used to log the `WithName` values as an additional attribute.
|
||||
nameKey = "logger"
|
||||
|
||||
// errKey is used to log the error parameter of Error as an additional attribute.
|
||||
errKey = "err"
|
||||
)
|
||||
|
||||
type slogSink struct {
|
||||
callDepth int
|
||||
name string
|
||||
handler slog.Handler
|
||||
}
|
||||
|
||||
func (l *slogSink) Init(info RuntimeInfo) {
|
||||
l.callDepth = info.CallDepth
|
||||
}
|
||||
|
||||
func (l *slogSink) GetUnderlying() slog.Handler {
|
||||
return l.handler
|
||||
}
|
||||
|
||||
func (l *slogSink) WithCallDepth(depth int) LogSink {
|
||||
newLogger := *l
|
||||
newLogger.callDepth += depth
|
||||
return &newLogger
|
||||
}
|
||||
|
||||
func (l *slogSink) Enabled(level int) bool {
|
||||
return l.handler.Enabled(context.Background(), slog.Level(-level))
|
||||
}
|
||||
|
||||
func (l *slogSink) Info(level int, msg string, kvList ...interface{}) {
|
||||
l.log(nil, msg, slog.Level(-level), kvList...)
|
||||
}
|
||||
|
||||
func (l *slogSink) Error(err error, msg string, kvList ...interface{}) {
|
||||
l.log(err, msg, slog.LevelError, kvList...)
|
||||
}
|
||||
|
||||
func (l *slogSink) log(err error, msg string, level slog.Level, kvList ...interface{}) {
|
||||
var pcs [1]uintptr
|
||||
// skip runtime.Callers, this function, Info/Error, and all helper functions above that.
|
||||
runtime.Callers(3+l.callDepth, pcs[:])
|
||||
|
||||
record := slog.NewRecord(time.Now(), level, msg, pcs[0])
|
||||
if l.name != "" {
|
||||
record.AddAttrs(slog.String(nameKey, l.name))
|
||||
}
|
||||
if err != nil {
|
||||
record.AddAttrs(slog.Any(errKey, err))
|
||||
}
|
||||
record.Add(kvList...)
|
||||
_ = l.handler.Handle(context.Background(), record)
|
||||
}
|
||||
|
||||
func (l slogSink) WithName(name string) LogSink {
|
||||
if l.name != "" {
|
||||
l.name += "/"
|
||||
}
|
||||
l.name += name
|
||||
return &l
|
||||
}
|
||||
|
||||
func (l slogSink) WithValues(kvList ...interface{}) LogSink {
|
||||
l.handler = l.handler.WithAttrs(kvListToAttrs(kvList...))
|
||||
return &l
|
||||
}
|
||||
|
||||
func kvListToAttrs(kvList ...interface{}) []slog.Attr {
|
||||
// We don't need the record itself, only its Add method.
|
||||
record := slog.NewRecord(time.Time{}, 0, "", 0)
|
||||
record.Add(kvList...)
|
||||
attrs := make([]slog.Attr, 0, record.NumAttrs())
|
||||
record.Attrs(func(attr slog.Attr) bool {
|
||||
attrs = append(attrs, attr)
|
||||
return true
|
||||
})
|
||||
return attrs
|
||||
}
|
||||
-530
@@ -1,530 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonpb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
protoV2 "google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
)
|
||||
|
||||
const wrapJSONUnmarshalV2 = false
|
||||
|
||||
// UnmarshalNext unmarshals the next JSON object from d into m.
|
||||
func UnmarshalNext(d *json.Decoder, m proto.Message) error {
|
||||
return new(Unmarshaler).UnmarshalNext(d, m)
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals a JSON object from r into m.
|
||||
func Unmarshal(r io.Reader, m proto.Message) error {
|
||||
return new(Unmarshaler).Unmarshal(r, m)
|
||||
}
|
||||
|
||||
// UnmarshalString unmarshals a JSON object from s into m.
|
||||
func UnmarshalString(s string, m proto.Message) error {
|
||||
return new(Unmarshaler).Unmarshal(strings.NewReader(s), m)
|
||||
}
|
||||
|
||||
// Unmarshaler is a configurable object for converting from a JSON
|
||||
// representation to a protocol buffer object.
|
||||
type Unmarshaler struct {
|
||||
// AllowUnknownFields specifies whether to allow messages to contain
|
||||
// unknown JSON fields, as opposed to failing to unmarshal.
|
||||
AllowUnknownFields bool
|
||||
|
||||
// AnyResolver is used to resolve the google.protobuf.Any well-known type.
|
||||
// If unset, the global registry is used by default.
|
||||
AnyResolver AnyResolver
|
||||
}
|
||||
|
||||
// JSONPBUnmarshaler is implemented by protobuf messages that customize the way
|
||||
// they are unmarshaled from JSON. Messages that implement this should also
|
||||
// implement JSONPBMarshaler so that the custom format can be produced.
|
||||
//
|
||||
// The JSON unmarshaling must follow the JSON to proto specification:
|
||||
// https://developers.google.com/protocol-buffers/docs/proto3#json
|
||||
//
|
||||
// Deprecated: Custom types should implement protobuf reflection instead.
|
||||
type JSONPBUnmarshaler interface {
|
||||
UnmarshalJSONPB(*Unmarshaler, []byte) error
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals a JSON object from r into m.
|
||||
func (u *Unmarshaler) Unmarshal(r io.Reader, m proto.Message) error {
|
||||
return u.UnmarshalNext(json.NewDecoder(r), m)
|
||||
}
|
||||
|
||||
// UnmarshalNext unmarshals the next JSON object from d into m.
|
||||
func (u *Unmarshaler) UnmarshalNext(d *json.Decoder, m proto.Message) error {
|
||||
if m == nil {
|
||||
return errors.New("invalid nil message")
|
||||
}
|
||||
|
||||
// Parse the next JSON object from the stream.
|
||||
raw := json.RawMessage{}
|
||||
if err := d.Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check for custom unmarshalers first since they may not properly
|
||||
// implement protobuf reflection that the logic below relies on.
|
||||
if jsu, ok := m.(JSONPBUnmarshaler); ok {
|
||||
return jsu.UnmarshalJSONPB(u, raw)
|
||||
}
|
||||
|
||||
mr := proto.MessageReflect(m)
|
||||
|
||||
// NOTE: For historical reasons, a top-level null is treated as a noop.
|
||||
// This is incorrect, but kept for compatibility.
|
||||
if string(raw) == "null" && mr.Descriptor().FullName() != "google.protobuf.Value" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if wrapJSONUnmarshalV2 {
|
||||
// NOTE: If input message is non-empty, we need to preserve merge semantics
|
||||
// of the old jsonpb implementation. These semantics are not supported by
|
||||
// the protobuf JSON specification.
|
||||
isEmpty := true
|
||||
mr.Range(func(protoreflect.FieldDescriptor, protoreflect.Value) bool {
|
||||
isEmpty = false // at least one iteration implies non-empty
|
||||
return false
|
||||
})
|
||||
if !isEmpty {
|
||||
// Perform unmarshaling into a newly allocated, empty message.
|
||||
mr = mr.New()
|
||||
|
||||
// Use a defer to copy all unmarshaled fields into the original message.
|
||||
dst := proto.MessageReflect(m)
|
||||
defer mr.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
|
||||
dst.Set(fd, v)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Unmarshal using the v2 JSON unmarshaler.
|
||||
opts := protojson.UnmarshalOptions{
|
||||
DiscardUnknown: u.AllowUnknownFields,
|
||||
}
|
||||
if u.AnyResolver != nil {
|
||||
opts.Resolver = anyResolver{u.AnyResolver}
|
||||
}
|
||||
return opts.Unmarshal(raw, mr.Interface())
|
||||
} else {
|
||||
if err := u.unmarshalMessage(mr, raw); err != nil {
|
||||
return err
|
||||
}
|
||||
return protoV2.CheckInitialized(mr.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) unmarshalMessage(m protoreflect.Message, in []byte) error {
|
||||
md := m.Descriptor()
|
||||
fds := md.Fields()
|
||||
|
||||
if jsu, ok := proto.MessageV1(m.Interface()).(JSONPBUnmarshaler); ok {
|
||||
return jsu.UnmarshalJSONPB(u, in)
|
||||
}
|
||||
|
||||
if string(in) == "null" && md.FullName() != "google.protobuf.Value" {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch wellKnownType(md.FullName()) {
|
||||
case "Any":
|
||||
var jsonObject map[string]json.RawMessage
|
||||
if err := json.Unmarshal(in, &jsonObject); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawTypeURL, ok := jsonObject["@type"]
|
||||
if !ok {
|
||||
return errors.New("Any JSON doesn't have '@type'")
|
||||
}
|
||||
typeURL, err := unquoteString(string(rawTypeURL))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't unmarshal Any's '@type': %q", rawTypeURL)
|
||||
}
|
||||
m.Set(fds.ByNumber(1), protoreflect.ValueOfString(typeURL))
|
||||
|
||||
var m2 protoreflect.Message
|
||||
if u.AnyResolver != nil {
|
||||
mi, err := u.AnyResolver.Resolve(typeURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m2 = proto.MessageReflect(mi)
|
||||
} else {
|
||||
mt, err := protoregistry.GlobalTypes.FindMessageByURL(typeURL)
|
||||
if err != nil {
|
||||
if err == protoregistry.NotFound {
|
||||
return fmt.Errorf("could not resolve Any message type: %v", typeURL)
|
||||
}
|
||||
return err
|
||||
}
|
||||
m2 = mt.New()
|
||||
}
|
||||
|
||||
if wellKnownType(m2.Descriptor().FullName()) != "" {
|
||||
rawValue, ok := jsonObject["value"]
|
||||
if !ok {
|
||||
return errors.New("Any JSON doesn't have 'value'")
|
||||
}
|
||||
if err := u.unmarshalMessage(m2, rawValue); err != nil {
|
||||
return fmt.Errorf("can't unmarshal Any nested proto %v: %v", typeURL, err)
|
||||
}
|
||||
} else {
|
||||
delete(jsonObject, "@type")
|
||||
rawJSON, err := json.Marshal(jsonObject)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't generate JSON for Any's nested proto to be unmarshaled: %v", err)
|
||||
}
|
||||
if err = u.unmarshalMessage(m2, rawJSON); err != nil {
|
||||
return fmt.Errorf("can't unmarshal Any nested proto %v: %v", typeURL, err)
|
||||
}
|
||||
}
|
||||
|
||||
rawWire, err := protoV2.Marshal(m2.Interface())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't marshal proto %v into Any.Value: %v", typeURL, err)
|
||||
}
|
||||
m.Set(fds.ByNumber(2), protoreflect.ValueOfBytes(rawWire))
|
||||
return nil
|
||||
case "BoolValue", "BytesValue", "StringValue",
|
||||
"Int32Value", "UInt32Value", "FloatValue",
|
||||
"Int64Value", "UInt64Value", "DoubleValue":
|
||||
fd := fds.ByNumber(1)
|
||||
v, err := u.unmarshalValue(m.NewField(fd), in, fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Set(fd, v)
|
||||
return nil
|
||||
case "Duration":
|
||||
v, err := unquoteString(string(in))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad Duration: %v", err)
|
||||
}
|
||||
|
||||
sec := d.Nanoseconds() / 1e9
|
||||
nsec := d.Nanoseconds() % 1e9
|
||||
m.Set(fds.ByNumber(1), protoreflect.ValueOfInt64(int64(sec)))
|
||||
m.Set(fds.ByNumber(2), protoreflect.ValueOfInt32(int32(nsec)))
|
||||
return nil
|
||||
case "Timestamp":
|
||||
v, err := unquoteString(string(in))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := time.Parse(time.RFC3339Nano, v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad Timestamp: %v", err)
|
||||
}
|
||||
|
||||
sec := t.Unix()
|
||||
nsec := t.Nanosecond()
|
||||
m.Set(fds.ByNumber(1), protoreflect.ValueOfInt64(int64(sec)))
|
||||
m.Set(fds.ByNumber(2), protoreflect.ValueOfInt32(int32(nsec)))
|
||||
return nil
|
||||
case "Value":
|
||||
switch {
|
||||
case string(in) == "null":
|
||||
m.Set(fds.ByNumber(1), protoreflect.ValueOfEnum(0))
|
||||
case string(in) == "true":
|
||||
m.Set(fds.ByNumber(4), protoreflect.ValueOfBool(true))
|
||||
case string(in) == "false":
|
||||
m.Set(fds.ByNumber(4), protoreflect.ValueOfBool(false))
|
||||
case hasPrefixAndSuffix('"', in, '"'):
|
||||
s, err := unquoteString(string(in))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unrecognized type for Value %q", in)
|
||||
}
|
||||
m.Set(fds.ByNumber(3), protoreflect.ValueOfString(s))
|
||||
case hasPrefixAndSuffix('[', in, ']'):
|
||||
v := m.Mutable(fds.ByNumber(6))
|
||||
return u.unmarshalMessage(v.Message(), in)
|
||||
case hasPrefixAndSuffix('{', in, '}'):
|
||||
v := m.Mutable(fds.ByNumber(5))
|
||||
return u.unmarshalMessage(v.Message(), in)
|
||||
default:
|
||||
f, err := strconv.ParseFloat(string(in), 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unrecognized type for Value %q", in)
|
||||
}
|
||||
m.Set(fds.ByNumber(2), protoreflect.ValueOfFloat64(f))
|
||||
}
|
||||
return nil
|
||||
case "ListValue":
|
||||
var jsonArray []json.RawMessage
|
||||
if err := json.Unmarshal(in, &jsonArray); err != nil {
|
||||
return fmt.Errorf("bad ListValue: %v", err)
|
||||
}
|
||||
|
||||
lv := m.Mutable(fds.ByNumber(1)).List()
|
||||
for _, raw := range jsonArray {
|
||||
ve := lv.NewElement()
|
||||
if err := u.unmarshalMessage(ve.Message(), raw); err != nil {
|
||||
return err
|
||||
}
|
||||
lv.Append(ve)
|
||||
}
|
||||
return nil
|
||||
case "Struct":
|
||||
var jsonObject map[string]json.RawMessage
|
||||
if err := json.Unmarshal(in, &jsonObject); err != nil {
|
||||
return fmt.Errorf("bad StructValue: %v", err)
|
||||
}
|
||||
|
||||
mv := m.Mutable(fds.ByNumber(1)).Map()
|
||||
for key, raw := range jsonObject {
|
||||
kv := protoreflect.ValueOf(key).MapKey()
|
||||
vv := mv.NewValue()
|
||||
if err := u.unmarshalMessage(vv.Message(), raw); err != nil {
|
||||
return fmt.Errorf("bad value in StructValue for key %q: %v", key, err)
|
||||
}
|
||||
mv.Set(kv, vv)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var jsonObject map[string]json.RawMessage
|
||||
if err := json.Unmarshal(in, &jsonObject); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle known fields.
|
||||
for i := 0; i < fds.Len(); i++ {
|
||||
fd := fds.Get(i)
|
||||
if fd.IsWeak() && fd.Message().IsPlaceholder() {
|
||||
continue // weak reference is not linked in
|
||||
}
|
||||
|
||||
// Search for any raw JSON value associated with this field.
|
||||
var raw json.RawMessage
|
||||
name := string(fd.Name())
|
||||
if fd.Kind() == protoreflect.GroupKind {
|
||||
name = string(fd.Message().Name())
|
||||
}
|
||||
if v, ok := jsonObject[name]; ok {
|
||||
delete(jsonObject, name)
|
||||
raw = v
|
||||
}
|
||||
name = string(fd.JSONName())
|
||||
if v, ok := jsonObject[name]; ok {
|
||||
delete(jsonObject, name)
|
||||
raw = v
|
||||
}
|
||||
|
||||
field := m.NewField(fd)
|
||||
// Unmarshal the field value.
|
||||
if raw == nil || (string(raw) == "null" && !isSingularWellKnownValue(fd) && !isSingularJSONPBUnmarshaler(field, fd)) {
|
||||
continue
|
||||
}
|
||||
v, err := u.unmarshalValue(field, raw, fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Set(fd, v)
|
||||
}
|
||||
|
||||
// Handle extension fields.
|
||||
for name, raw := range jsonObject {
|
||||
if !strings.HasPrefix(name, "[") || !strings.HasSuffix(name, "]") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Resolve the extension field by name.
|
||||
xname := protoreflect.FullName(name[len("[") : len(name)-len("]")])
|
||||
xt, _ := protoregistry.GlobalTypes.FindExtensionByName(xname)
|
||||
if xt == nil && isMessageSet(md) {
|
||||
xt, _ = protoregistry.GlobalTypes.FindExtensionByName(xname.Append("message_set_extension"))
|
||||
}
|
||||
if xt == nil {
|
||||
continue
|
||||
}
|
||||
delete(jsonObject, name)
|
||||
fd := xt.TypeDescriptor()
|
||||
if fd.ContainingMessage().FullName() != m.Descriptor().FullName() {
|
||||
return fmt.Errorf("extension field %q does not extend message %q", xname, m.Descriptor().FullName())
|
||||
}
|
||||
|
||||
field := m.NewField(fd)
|
||||
// Unmarshal the field value.
|
||||
if raw == nil || (string(raw) == "null" && !isSingularWellKnownValue(fd) && !isSingularJSONPBUnmarshaler(field, fd)) {
|
||||
continue
|
||||
}
|
||||
v, err := u.unmarshalValue(field, raw, fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Set(fd, v)
|
||||
}
|
||||
|
||||
if !u.AllowUnknownFields && len(jsonObject) > 0 {
|
||||
for name := range jsonObject {
|
||||
return fmt.Errorf("unknown field %q in %v", name, md.FullName())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSingularWellKnownValue(fd protoreflect.FieldDescriptor) bool {
|
||||
if fd.Cardinality() == protoreflect.Repeated {
|
||||
return false
|
||||
}
|
||||
if md := fd.Message(); md != nil {
|
||||
return md.FullName() == "google.protobuf.Value"
|
||||
}
|
||||
if ed := fd.Enum(); ed != nil {
|
||||
return ed.FullName() == "google.protobuf.NullValue"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isSingularJSONPBUnmarshaler(v protoreflect.Value, fd protoreflect.FieldDescriptor) bool {
|
||||
if fd.Message() != nil && fd.Cardinality() != protoreflect.Repeated {
|
||||
_, ok := proto.MessageV1(v.Interface()).(JSONPBUnmarshaler)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) unmarshalValue(v protoreflect.Value, in []byte, fd protoreflect.FieldDescriptor) (protoreflect.Value, error) {
|
||||
switch {
|
||||
case fd.IsList():
|
||||
var jsonArray []json.RawMessage
|
||||
if err := json.Unmarshal(in, &jsonArray); err != nil {
|
||||
return v, err
|
||||
}
|
||||
lv := v.List()
|
||||
for _, raw := range jsonArray {
|
||||
ve, err := u.unmarshalSingularValue(lv.NewElement(), raw, fd)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
lv.Append(ve)
|
||||
}
|
||||
return v, nil
|
||||
case fd.IsMap():
|
||||
var jsonObject map[string]json.RawMessage
|
||||
if err := json.Unmarshal(in, &jsonObject); err != nil {
|
||||
return v, err
|
||||
}
|
||||
kfd := fd.MapKey()
|
||||
vfd := fd.MapValue()
|
||||
mv := v.Map()
|
||||
for key, raw := range jsonObject {
|
||||
var kv protoreflect.MapKey
|
||||
if kfd.Kind() == protoreflect.StringKind {
|
||||
kv = protoreflect.ValueOf(key).MapKey()
|
||||
} else {
|
||||
v, err := u.unmarshalSingularValue(kfd.Default(), []byte(key), kfd)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
kv = v.MapKey()
|
||||
}
|
||||
|
||||
vv, err := u.unmarshalSingularValue(mv.NewValue(), raw, vfd)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
mv.Set(kv, vv)
|
||||
}
|
||||
return v, nil
|
||||
default:
|
||||
return u.unmarshalSingularValue(v, in, fd)
|
||||
}
|
||||
}
|
||||
|
||||
var nonFinite = map[string]float64{
|
||||
`"NaN"`: math.NaN(),
|
||||
`"Infinity"`: math.Inf(+1),
|
||||
`"-Infinity"`: math.Inf(-1),
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) unmarshalSingularValue(v protoreflect.Value, in []byte, fd protoreflect.FieldDescriptor) (protoreflect.Value, error) {
|
||||
switch fd.Kind() {
|
||||
case protoreflect.BoolKind:
|
||||
return unmarshalValue(in, new(bool))
|
||||
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
|
||||
return unmarshalValue(trimQuote(in), new(int32))
|
||||
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
|
||||
return unmarshalValue(trimQuote(in), new(int64))
|
||||
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
|
||||
return unmarshalValue(trimQuote(in), new(uint32))
|
||||
case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
|
||||
return unmarshalValue(trimQuote(in), new(uint64))
|
||||
case protoreflect.FloatKind:
|
||||
if f, ok := nonFinite[string(in)]; ok {
|
||||
return protoreflect.ValueOfFloat32(float32(f)), nil
|
||||
}
|
||||
return unmarshalValue(trimQuote(in), new(float32))
|
||||
case protoreflect.DoubleKind:
|
||||
if f, ok := nonFinite[string(in)]; ok {
|
||||
return protoreflect.ValueOfFloat64(float64(f)), nil
|
||||
}
|
||||
return unmarshalValue(trimQuote(in), new(float64))
|
||||
case protoreflect.StringKind:
|
||||
return unmarshalValue(in, new(string))
|
||||
case protoreflect.BytesKind:
|
||||
return unmarshalValue(in, new([]byte))
|
||||
case protoreflect.EnumKind:
|
||||
if hasPrefixAndSuffix('"', in, '"') {
|
||||
vd := fd.Enum().Values().ByName(protoreflect.Name(trimQuote(in)))
|
||||
if vd == nil {
|
||||
return v, fmt.Errorf("unknown value %q for enum %s", in, fd.Enum().FullName())
|
||||
}
|
||||
return protoreflect.ValueOfEnum(vd.Number()), nil
|
||||
}
|
||||
return unmarshalValue(in, new(protoreflect.EnumNumber))
|
||||
case protoreflect.MessageKind, protoreflect.GroupKind:
|
||||
err := u.unmarshalMessage(v.Message(), in)
|
||||
return v, err
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid kind %v", fd.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalValue(in []byte, v interface{}) (protoreflect.Value, error) {
|
||||
err := json.Unmarshal(in, v)
|
||||
return protoreflect.ValueOf(reflect.ValueOf(v).Elem().Interface()), err
|
||||
}
|
||||
|
||||
func unquoteString(in string) (out string, err error) {
|
||||
err = json.Unmarshal([]byte(in), &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func hasPrefixAndSuffix(prefix byte, in []byte, suffix byte) bool {
|
||||
if len(in) >= 2 && in[0] == prefix && in[len(in)-1] == suffix {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// trimQuote is like unquoteString but simply strips surrounding quotes.
|
||||
// This is incorrect, but is behavior done by the legacy implementation.
|
||||
func trimQuote(in []byte) []byte {
|
||||
if len(in) >= 2 && in[0] == '"' && in[len(in)-1] == '"' {
|
||||
in = in[1 : len(in)-1]
|
||||
}
|
||||
return in
|
||||
}
|
||||
-559
@@ -1,559 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonpb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
protoV2 "google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
)
|
||||
|
||||
const wrapJSONMarshalV2 = false
|
||||
|
||||
// Marshaler is a configurable object for marshaling protocol buffer messages
|
||||
// to the specified JSON representation.
|
||||
type Marshaler struct {
|
||||
// OrigName specifies whether to use the original protobuf name for fields.
|
||||
OrigName bool
|
||||
|
||||
// EnumsAsInts specifies whether to render enum values as integers,
|
||||
// as opposed to string values.
|
||||
EnumsAsInts bool
|
||||
|
||||
// EmitDefaults specifies whether to render fields with zero values.
|
||||
EmitDefaults bool
|
||||
|
||||
// Indent controls whether the output is compact or not.
|
||||
// If empty, the output is compact JSON. Otherwise, every JSON object
|
||||
// entry and JSON array value will be on its own line.
|
||||
// Each line will be preceded by repeated copies of Indent, where the
|
||||
// number of copies is the current indentation depth.
|
||||
Indent string
|
||||
|
||||
// AnyResolver is used to resolve the google.protobuf.Any well-known type.
|
||||
// If unset, the global registry is used by default.
|
||||
AnyResolver AnyResolver
|
||||
}
|
||||
|
||||
// JSONPBMarshaler is implemented by protobuf messages that customize the
|
||||
// way they are marshaled to JSON. Messages that implement this should also
|
||||
// implement JSONPBUnmarshaler so that the custom format can be parsed.
|
||||
//
|
||||
// The JSON marshaling must follow the proto to JSON specification:
|
||||
// https://developers.google.com/protocol-buffers/docs/proto3#json
|
||||
//
|
||||
// Deprecated: Custom types should implement protobuf reflection instead.
|
||||
type JSONPBMarshaler interface {
|
||||
MarshalJSONPB(*Marshaler) ([]byte, error)
|
||||
}
|
||||
|
||||
// Marshal serializes a protobuf message as JSON into w.
|
||||
func (jm *Marshaler) Marshal(w io.Writer, m proto.Message) error {
|
||||
b, err := jm.marshal(m)
|
||||
if len(b) > 0 {
|
||||
if _, err := w.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalToString serializes a protobuf message as JSON in string form.
|
||||
func (jm *Marshaler) MarshalToString(m proto.Message) (string, error) {
|
||||
b, err := jm.marshal(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (jm *Marshaler) marshal(m proto.Message) ([]byte, error) {
|
||||
v := reflect.ValueOf(m)
|
||||
if m == nil || (v.Kind() == reflect.Ptr && v.IsNil()) {
|
||||
return nil, errors.New("Marshal called with nil")
|
||||
}
|
||||
|
||||
// Check for custom marshalers first since they may not properly
|
||||
// implement protobuf reflection that the logic below relies on.
|
||||
if jsm, ok := m.(JSONPBMarshaler); ok {
|
||||
return jsm.MarshalJSONPB(jm)
|
||||
}
|
||||
|
||||
if wrapJSONMarshalV2 {
|
||||
opts := protojson.MarshalOptions{
|
||||
UseProtoNames: jm.OrigName,
|
||||
UseEnumNumbers: jm.EnumsAsInts,
|
||||
EmitUnpopulated: jm.EmitDefaults,
|
||||
Indent: jm.Indent,
|
||||
}
|
||||
if jm.AnyResolver != nil {
|
||||
opts.Resolver = anyResolver{jm.AnyResolver}
|
||||
}
|
||||
return opts.Marshal(proto.MessageReflect(m).Interface())
|
||||
} else {
|
||||
// Check for unpopulated required fields first.
|
||||
m2 := proto.MessageReflect(m)
|
||||
if err := protoV2.CheckInitialized(m2.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w := jsonWriter{Marshaler: jm}
|
||||
err := w.marshalMessage(m2, "", "")
|
||||
return w.buf, err
|
||||
}
|
||||
}
|
||||
|
||||
type jsonWriter struct {
|
||||
*Marshaler
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (w *jsonWriter) write(s string) {
|
||||
w.buf = append(w.buf, s...)
|
||||
}
|
||||
|
||||
func (w *jsonWriter) marshalMessage(m protoreflect.Message, indent, typeURL string) error {
|
||||
if jsm, ok := proto.MessageV1(m.Interface()).(JSONPBMarshaler); ok {
|
||||
b, err := jsm.MarshalJSONPB(w.Marshaler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if typeURL != "" {
|
||||
// we are marshaling this object to an Any type
|
||||
var js map[string]*json.RawMessage
|
||||
if err = json.Unmarshal(b, &js); err != nil {
|
||||
return fmt.Errorf("type %T produced invalid JSON: %v", m.Interface(), err)
|
||||
}
|
||||
turl, err := json.Marshal(typeURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal type URL %q to JSON: %v", typeURL, err)
|
||||
}
|
||||
js["@type"] = (*json.RawMessage)(&turl)
|
||||
if b, err = json.Marshal(js); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.write(string(b))
|
||||
return nil
|
||||
}
|
||||
|
||||
md := m.Descriptor()
|
||||
fds := md.Fields()
|
||||
|
||||
// Handle well-known types.
|
||||
const secondInNanos = int64(time.Second / time.Nanosecond)
|
||||
switch wellKnownType(md.FullName()) {
|
||||
case "Any":
|
||||
return w.marshalAny(m, indent)
|
||||
case "BoolValue", "BytesValue", "StringValue",
|
||||
"Int32Value", "UInt32Value", "FloatValue",
|
||||
"Int64Value", "UInt64Value", "DoubleValue":
|
||||
fd := fds.ByNumber(1)
|
||||
return w.marshalValue(fd, m.Get(fd), indent)
|
||||
case "Duration":
|
||||
const maxSecondsInDuration = 315576000000
|
||||
// "Generated output always contains 0, 3, 6, or 9 fractional digits,
|
||||
// depending on required precision."
|
||||
s := m.Get(fds.ByNumber(1)).Int()
|
||||
ns := m.Get(fds.ByNumber(2)).Int()
|
||||
if s < -maxSecondsInDuration || s > maxSecondsInDuration {
|
||||
return fmt.Errorf("seconds out of range %v", s)
|
||||
}
|
||||
if ns <= -secondInNanos || ns >= secondInNanos {
|
||||
return fmt.Errorf("ns out of range (%v, %v)", -secondInNanos, secondInNanos)
|
||||
}
|
||||
if (s > 0 && ns < 0) || (s < 0 && ns > 0) {
|
||||
return errors.New("signs of seconds and nanos do not match")
|
||||
}
|
||||
var sign string
|
||||
if s < 0 || ns < 0 {
|
||||
sign, s, ns = "-", -1*s, -1*ns
|
||||
}
|
||||
x := fmt.Sprintf("%s%d.%09d", sign, s, ns)
|
||||
x = strings.TrimSuffix(x, "000")
|
||||
x = strings.TrimSuffix(x, "000")
|
||||
x = strings.TrimSuffix(x, ".000")
|
||||
w.write(fmt.Sprintf(`"%vs"`, x))
|
||||
return nil
|
||||
case "Timestamp":
|
||||
// "RFC 3339, where generated output will always be Z-normalized
|
||||
// and uses 0, 3, 6 or 9 fractional digits."
|
||||
s := m.Get(fds.ByNumber(1)).Int()
|
||||
ns := m.Get(fds.ByNumber(2)).Int()
|
||||
if ns < 0 || ns >= secondInNanos {
|
||||
return fmt.Errorf("ns out of range [0, %v)", secondInNanos)
|
||||
}
|
||||
t := time.Unix(s, ns).UTC()
|
||||
// time.RFC3339Nano isn't exactly right (we need to get 3/6/9 fractional digits).
|
||||
x := t.Format("2006-01-02T15:04:05.000000000")
|
||||
x = strings.TrimSuffix(x, "000")
|
||||
x = strings.TrimSuffix(x, "000")
|
||||
x = strings.TrimSuffix(x, ".000")
|
||||
w.write(fmt.Sprintf(`"%vZ"`, x))
|
||||
return nil
|
||||
case "Value":
|
||||
// JSON value; which is a null, number, string, bool, object, or array.
|
||||
od := md.Oneofs().Get(0)
|
||||
fd := m.WhichOneof(od)
|
||||
if fd == nil {
|
||||
return errors.New("nil Value")
|
||||
}
|
||||
return w.marshalValue(fd, m.Get(fd), indent)
|
||||
case "Struct", "ListValue":
|
||||
// JSON object or array.
|
||||
fd := fds.ByNumber(1)
|
||||
return w.marshalValue(fd, m.Get(fd), indent)
|
||||
}
|
||||
|
||||
w.write("{")
|
||||
if w.Indent != "" {
|
||||
w.write("\n")
|
||||
}
|
||||
|
||||
firstField := true
|
||||
if typeURL != "" {
|
||||
if err := w.marshalTypeURL(indent, typeURL); err != nil {
|
||||
return err
|
||||
}
|
||||
firstField = false
|
||||
}
|
||||
|
||||
for i := 0; i < fds.Len(); {
|
||||
fd := fds.Get(i)
|
||||
if od := fd.ContainingOneof(); od != nil {
|
||||
fd = m.WhichOneof(od)
|
||||
i += od.Fields().Len()
|
||||
if fd == nil {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
|
||||
v := m.Get(fd)
|
||||
|
||||
if !m.Has(fd) {
|
||||
if !w.EmitDefaults || fd.ContainingOneof() != nil {
|
||||
continue
|
||||
}
|
||||
if fd.Cardinality() != protoreflect.Repeated && (fd.Message() != nil || fd.Syntax() == protoreflect.Proto2) {
|
||||
v = protoreflect.Value{} // use "null" for singular messages or proto2 scalars
|
||||
}
|
||||
}
|
||||
|
||||
if !firstField {
|
||||
w.writeComma()
|
||||
}
|
||||
if err := w.marshalField(fd, v, indent); err != nil {
|
||||
return err
|
||||
}
|
||||
firstField = false
|
||||
}
|
||||
|
||||
// Handle proto2 extensions.
|
||||
if md.ExtensionRanges().Len() > 0 {
|
||||
// Collect a sorted list of all extension descriptor and values.
|
||||
type ext struct {
|
||||
desc protoreflect.FieldDescriptor
|
||||
val protoreflect.Value
|
||||
}
|
||||
var exts []ext
|
||||
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
|
||||
if fd.IsExtension() {
|
||||
exts = append(exts, ext{fd, v})
|
||||
}
|
||||
return true
|
||||
})
|
||||
sort.Slice(exts, func(i, j int) bool {
|
||||
return exts[i].desc.Number() < exts[j].desc.Number()
|
||||
})
|
||||
|
||||
for _, ext := range exts {
|
||||
if !firstField {
|
||||
w.writeComma()
|
||||
}
|
||||
if err := w.marshalField(ext.desc, ext.val, indent); err != nil {
|
||||
return err
|
||||
}
|
||||
firstField = false
|
||||
}
|
||||
}
|
||||
|
||||
if w.Indent != "" {
|
||||
w.write("\n")
|
||||
w.write(indent)
|
||||
}
|
||||
w.write("}")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *jsonWriter) writeComma() {
|
||||
if w.Indent != "" {
|
||||
w.write(",\n")
|
||||
} else {
|
||||
w.write(",")
|
||||
}
|
||||
}
|
||||
|
||||
func (w *jsonWriter) marshalAny(m protoreflect.Message, indent string) error {
|
||||
// "If the Any contains a value that has a special JSON mapping,
|
||||
// it will be converted as follows: {"@type": xxx, "value": yyy}.
|
||||
// Otherwise, the value will be converted into a JSON object,
|
||||
// and the "@type" field will be inserted to indicate the actual data type."
|
||||
md := m.Descriptor()
|
||||
typeURL := m.Get(md.Fields().ByNumber(1)).String()
|
||||
rawVal := m.Get(md.Fields().ByNumber(2)).Bytes()
|
||||
|
||||
var m2 protoreflect.Message
|
||||
if w.AnyResolver != nil {
|
||||
mi, err := w.AnyResolver.Resolve(typeURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m2 = proto.MessageReflect(mi)
|
||||
} else {
|
||||
mt, err := protoregistry.GlobalTypes.FindMessageByURL(typeURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m2 = mt.New()
|
||||
}
|
||||
|
||||
if err := protoV2.Unmarshal(rawVal, m2.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if wellKnownType(m2.Descriptor().FullName()) == "" {
|
||||
return w.marshalMessage(m2, indent, typeURL)
|
||||
}
|
||||
|
||||
w.write("{")
|
||||
if w.Indent != "" {
|
||||
w.write("\n")
|
||||
}
|
||||
if err := w.marshalTypeURL(indent, typeURL); err != nil {
|
||||
return err
|
||||
}
|
||||
w.writeComma()
|
||||
if w.Indent != "" {
|
||||
w.write(indent)
|
||||
w.write(w.Indent)
|
||||
w.write(`"value": `)
|
||||
} else {
|
||||
w.write(`"value":`)
|
||||
}
|
||||
if err := w.marshalMessage(m2, indent+w.Indent, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
if w.Indent != "" {
|
||||
w.write("\n")
|
||||
w.write(indent)
|
||||
}
|
||||
w.write("}")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *jsonWriter) marshalTypeURL(indent, typeURL string) error {
|
||||
if w.Indent != "" {
|
||||
w.write(indent)
|
||||
w.write(w.Indent)
|
||||
}
|
||||
w.write(`"@type":`)
|
||||
if w.Indent != "" {
|
||||
w.write(" ")
|
||||
}
|
||||
b, err := json.Marshal(typeURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.write(string(b))
|
||||
return nil
|
||||
}
|
||||
|
||||
// marshalField writes field description and value to the Writer.
|
||||
func (w *jsonWriter) marshalField(fd protoreflect.FieldDescriptor, v protoreflect.Value, indent string) error {
|
||||
if w.Indent != "" {
|
||||
w.write(indent)
|
||||
w.write(w.Indent)
|
||||
}
|
||||
w.write(`"`)
|
||||
switch {
|
||||
case fd.IsExtension():
|
||||
// For message set, use the fname of the message as the extension name.
|
||||
name := string(fd.FullName())
|
||||
if isMessageSet(fd.ContainingMessage()) {
|
||||
name = strings.TrimSuffix(name, ".message_set_extension")
|
||||
}
|
||||
|
||||
w.write("[" + name + "]")
|
||||
case w.OrigName:
|
||||
name := string(fd.Name())
|
||||
if fd.Kind() == protoreflect.GroupKind {
|
||||
name = string(fd.Message().Name())
|
||||
}
|
||||
w.write(name)
|
||||
default:
|
||||
w.write(string(fd.JSONName()))
|
||||
}
|
||||
w.write(`":`)
|
||||
if w.Indent != "" {
|
||||
w.write(" ")
|
||||
}
|
||||
return w.marshalValue(fd, v, indent)
|
||||
}
|
||||
|
||||
func (w *jsonWriter) marshalValue(fd protoreflect.FieldDescriptor, v protoreflect.Value, indent string) error {
|
||||
switch {
|
||||
case fd.IsList():
|
||||
w.write("[")
|
||||
comma := ""
|
||||
lv := v.List()
|
||||
for i := 0; i < lv.Len(); i++ {
|
||||
w.write(comma)
|
||||
if w.Indent != "" {
|
||||
w.write("\n")
|
||||
w.write(indent)
|
||||
w.write(w.Indent)
|
||||
w.write(w.Indent)
|
||||
}
|
||||
if err := w.marshalSingularValue(fd, lv.Get(i), indent+w.Indent); err != nil {
|
||||
return err
|
||||
}
|
||||
comma = ","
|
||||
}
|
||||
if w.Indent != "" {
|
||||
w.write("\n")
|
||||
w.write(indent)
|
||||
w.write(w.Indent)
|
||||
}
|
||||
w.write("]")
|
||||
return nil
|
||||
case fd.IsMap():
|
||||
kfd := fd.MapKey()
|
||||
vfd := fd.MapValue()
|
||||
mv := v.Map()
|
||||
|
||||
// Collect a sorted list of all map keys and values.
|
||||
type entry struct{ key, val protoreflect.Value }
|
||||
var entries []entry
|
||||
mv.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool {
|
||||
entries = append(entries, entry{k.Value(), v})
|
||||
return true
|
||||
})
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
switch kfd.Kind() {
|
||||
case protoreflect.BoolKind:
|
||||
return !entries[i].key.Bool() && entries[j].key.Bool()
|
||||
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind, protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
|
||||
return entries[i].key.Int() < entries[j].key.Int()
|
||||
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind, protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
|
||||
return entries[i].key.Uint() < entries[j].key.Uint()
|
||||
case protoreflect.StringKind:
|
||||
return entries[i].key.String() < entries[j].key.String()
|
||||
default:
|
||||
panic("invalid kind")
|
||||
}
|
||||
})
|
||||
|
||||
w.write(`{`)
|
||||
comma := ""
|
||||
for _, entry := range entries {
|
||||
w.write(comma)
|
||||
if w.Indent != "" {
|
||||
w.write("\n")
|
||||
w.write(indent)
|
||||
w.write(w.Indent)
|
||||
w.write(w.Indent)
|
||||
}
|
||||
|
||||
s := fmt.Sprint(entry.key.Interface())
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.write(string(b))
|
||||
|
||||
w.write(`:`)
|
||||
if w.Indent != "" {
|
||||
w.write(` `)
|
||||
}
|
||||
|
||||
if err := w.marshalSingularValue(vfd, entry.val, indent+w.Indent); err != nil {
|
||||
return err
|
||||
}
|
||||
comma = ","
|
||||
}
|
||||
if w.Indent != "" {
|
||||
w.write("\n")
|
||||
w.write(indent)
|
||||
w.write(w.Indent)
|
||||
}
|
||||
w.write(`}`)
|
||||
return nil
|
||||
default:
|
||||
return w.marshalSingularValue(fd, v, indent)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *jsonWriter) marshalSingularValue(fd protoreflect.FieldDescriptor, v protoreflect.Value, indent string) error {
|
||||
switch {
|
||||
case !v.IsValid():
|
||||
w.write("null")
|
||||
return nil
|
||||
case fd.Message() != nil:
|
||||
return w.marshalMessage(v.Message(), indent+w.Indent, "")
|
||||
case fd.Enum() != nil:
|
||||
if fd.Enum().FullName() == "google.protobuf.NullValue" {
|
||||
w.write("null")
|
||||
return nil
|
||||
}
|
||||
|
||||
vd := fd.Enum().Values().ByNumber(v.Enum())
|
||||
if vd == nil || w.EnumsAsInts {
|
||||
w.write(strconv.Itoa(int(v.Enum())))
|
||||
} else {
|
||||
w.write(`"` + string(vd.Name()) + `"`)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
switch v.Interface().(type) {
|
||||
case float32, float64:
|
||||
switch {
|
||||
case math.IsInf(v.Float(), +1):
|
||||
w.write(`"Infinity"`)
|
||||
return nil
|
||||
case math.IsInf(v.Float(), -1):
|
||||
w.write(`"-Infinity"`)
|
||||
return nil
|
||||
case math.IsNaN(v.Float()):
|
||||
w.write(`"NaN"`)
|
||||
return nil
|
||||
}
|
||||
case int64, uint64:
|
||||
w.write(fmt.Sprintf(`"%d"`, v.Interface()))
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := json.Marshal(v.Interface())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.write(string(b))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package jsonpb provides functionality to marshal and unmarshal between a
|
||||
// protocol buffer message and JSON. It follows the specification at
|
||||
// https://developers.google.com/protocol-buffers/docs/proto3#json.
|
||||
//
|
||||
// Do not rely on the default behavior of the standard encoding/json package
|
||||
// when called on generated message types as it does not operate correctly.
|
||||
//
|
||||
// Deprecated: Use the "google.golang.org/protobuf/encoding/protojson"
|
||||
// package instead.
|
||||
package jsonpb
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
"google.golang.org/protobuf/runtime/protoimpl"
|
||||
)
|
||||
|
||||
// AnyResolver takes a type URL, present in an Any message,
|
||||
// and resolves it into an instance of the associated message.
|
||||
type AnyResolver interface {
|
||||
Resolve(typeURL string) (proto.Message, error)
|
||||
}
|
||||
|
||||
type anyResolver struct{ AnyResolver }
|
||||
|
||||
func (r anyResolver) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error) {
|
||||
return r.FindMessageByURL(string(message))
|
||||
}
|
||||
|
||||
func (r anyResolver) FindMessageByURL(url string) (protoreflect.MessageType, error) {
|
||||
m, err := r.Resolve(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return protoimpl.X.MessageTypeOf(m), nil
|
||||
}
|
||||
|
||||
func (r anyResolver) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) {
|
||||
return protoregistry.GlobalTypes.FindExtensionByName(field)
|
||||
}
|
||||
|
||||
func (r anyResolver) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) {
|
||||
return protoregistry.GlobalTypes.FindExtensionByNumber(message, field)
|
||||
}
|
||||
|
||||
func wellKnownType(s protoreflect.FullName) string {
|
||||
if s.Parent() == "google.protobuf" {
|
||||
switch s.Name() {
|
||||
case "Empty", "Any",
|
||||
"BoolValue", "BytesValue", "StringValue",
|
||||
"Int32Value", "UInt32Value", "FloatValue",
|
||||
"Int64Value", "UInt64Value", "DoubleValue",
|
||||
"Duration", "Timestamp",
|
||||
"NullValue", "Struct", "Value", "ListValue":
|
||||
return string(s.Name())
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func isMessageSet(md protoreflect.MessageDescriptor) bool {
|
||||
ms, ok := md.(interface{ IsMessageSet() bool })
|
||||
return ok && ms.IsMessageSet()
|
||||
}
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ptypes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
|
||||
anypb "github.com/golang/protobuf/ptypes/any"
|
||||
)
|
||||
|
||||
const urlPrefix = "type.googleapis.com/"
|
||||
|
||||
// AnyMessageName returns the message name contained in an anypb.Any message.
|
||||
// Most type assertions should use the Is function instead.
|
||||
//
|
||||
// Deprecated: Call the any.MessageName method instead.
|
||||
func AnyMessageName(any *anypb.Any) (string, error) {
|
||||
name, err := anyMessageName(any)
|
||||
return string(name), err
|
||||
}
|
||||
func anyMessageName(any *anypb.Any) (protoreflect.FullName, error) {
|
||||
if any == nil {
|
||||
return "", fmt.Errorf("message is nil")
|
||||
}
|
||||
name := protoreflect.FullName(any.TypeUrl)
|
||||
if i := strings.LastIndex(any.TypeUrl, "/"); i >= 0 {
|
||||
name = name[i+len("/"):]
|
||||
}
|
||||
if !name.IsValid() {
|
||||
return "", fmt.Errorf("message type url %q is invalid", any.TypeUrl)
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// MarshalAny marshals the given message m into an anypb.Any message.
|
||||
//
|
||||
// Deprecated: Call the anypb.New function instead.
|
||||
func MarshalAny(m proto.Message) (*anypb.Any, error) {
|
||||
switch dm := m.(type) {
|
||||
case DynamicAny:
|
||||
m = dm.Message
|
||||
case *DynamicAny:
|
||||
if dm == nil {
|
||||
return nil, proto.ErrNil
|
||||
}
|
||||
m = dm.Message
|
||||
}
|
||||
b, err := proto.Marshal(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &anypb.Any{TypeUrl: urlPrefix + proto.MessageName(m), Value: b}, nil
|
||||
}
|
||||
|
||||
// Empty returns a new message of the type specified in an anypb.Any message.
|
||||
// It returns protoregistry.NotFound if the corresponding message type could not
|
||||
// be resolved in the global registry.
|
||||
//
|
||||
// Deprecated: Use protoregistry.GlobalTypes.FindMessageByName instead
|
||||
// to resolve the message name and create a new instance of it.
|
||||
func Empty(any *anypb.Any) (proto.Message, error) {
|
||||
name, err := anyMessageName(any)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mt, err := protoregistry.GlobalTypes.FindMessageByName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proto.MessageV1(mt.New().Interface()), nil
|
||||
}
|
||||
|
||||
// UnmarshalAny unmarshals the encoded value contained in the anypb.Any message
|
||||
// into the provided message m. It returns an error if the target message
|
||||
// does not match the type in the Any message or if an unmarshal error occurs.
|
||||
//
|
||||
// The target message m may be a *DynamicAny message. If the underlying message
|
||||
// type could not be resolved, then this returns protoregistry.NotFound.
|
||||
//
|
||||
// Deprecated: Call the any.UnmarshalTo method instead.
|
||||
func UnmarshalAny(any *anypb.Any, m proto.Message) error {
|
||||
if dm, ok := m.(*DynamicAny); ok {
|
||||
if dm.Message == nil {
|
||||
var err error
|
||||
dm.Message, err = Empty(any)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
m = dm.Message
|
||||
}
|
||||
|
||||
anyName, err := AnyMessageName(any)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgName := proto.MessageName(m)
|
||||
if anyName != msgName {
|
||||
return fmt.Errorf("mismatched message type: got %q want %q", anyName, msgName)
|
||||
}
|
||||
return proto.Unmarshal(any.Value, m)
|
||||
}
|
||||
|
||||
// Is reports whether the Any message contains a message of the specified type.
|
||||
//
|
||||
// Deprecated: Call the any.MessageIs method instead.
|
||||
func Is(any *anypb.Any, m proto.Message) bool {
|
||||
if any == nil || m == nil {
|
||||
return false
|
||||
}
|
||||
name := proto.MessageName(m)
|
||||
if !strings.HasSuffix(any.TypeUrl, name) {
|
||||
return false
|
||||
}
|
||||
return len(any.TypeUrl) == len(name) || any.TypeUrl[len(any.TypeUrl)-len(name)-1] == '/'
|
||||
}
|
||||
|
||||
// DynamicAny is a value that can be passed to UnmarshalAny to automatically
|
||||
// allocate a proto.Message for the type specified in an anypb.Any message.
|
||||
// The allocated message is stored in the embedded proto.Message.
|
||||
//
|
||||
// Example:
|
||||
// var x ptypes.DynamicAny
|
||||
// if err := ptypes.UnmarshalAny(a, &x); err != nil { ... }
|
||||
// fmt.Printf("unmarshaled message: %v", x.Message)
|
||||
//
|
||||
// Deprecated: Use the any.UnmarshalNew method instead to unmarshal
|
||||
// the any message contents into a new instance of the underlying message.
|
||||
type DynamicAny struct{ proto.Message }
|
||||
|
||||
func (m DynamicAny) String() string {
|
||||
if m.Message == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return m.Message.String()
|
||||
}
|
||||
func (m DynamicAny) Reset() {
|
||||
if m.Message == nil {
|
||||
return
|
||||
}
|
||||
m.Message.Reset()
|
||||
}
|
||||
func (m DynamicAny) ProtoMessage() {
|
||||
return
|
||||
}
|
||||
func (m DynamicAny) ProtoReflect() protoreflect.Message {
|
||||
if m.Message == nil {
|
||||
return nil
|
||||
}
|
||||
return dynamicAny{proto.MessageReflect(m.Message)}
|
||||
}
|
||||
|
||||
type dynamicAny struct{ protoreflect.Message }
|
||||
|
||||
func (m dynamicAny) Type() protoreflect.MessageType {
|
||||
return dynamicAnyType{m.Message.Type()}
|
||||
}
|
||||
func (m dynamicAny) New() protoreflect.Message {
|
||||
return dynamicAnyType{m.Message.Type()}.New()
|
||||
}
|
||||
func (m dynamicAny) Interface() protoreflect.ProtoMessage {
|
||||
return DynamicAny{proto.MessageV1(m.Message.Interface())}
|
||||
}
|
||||
|
||||
type dynamicAnyType struct{ protoreflect.MessageType }
|
||||
|
||||
func (t dynamicAnyType) New() protoreflect.Message {
|
||||
return dynamicAny{t.MessageType.New()}
|
||||
}
|
||||
func (t dynamicAnyType) Zero() protoreflect.Message {
|
||||
return dynamicAny{t.MessageType.Zero()}
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: github.com/golang/protobuf/ptypes/any/any.proto
|
||||
|
||||
package any
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
anypb "google.golang.org/protobuf/types/known/anypb"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// Symbols defined in public import of google/protobuf/any.proto.
|
||||
|
||||
type Any = anypb.Any
|
||||
|
||||
var File_github_com_golang_protobuf_ptypes_any_any_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_github_com_golang_protobuf_ptypes_any_any_proto_rawDesc = []byte{
|
||||
0x0a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c,
|
||||
0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79,
|
||||
0x70, 0x65, 0x73, 0x2f, 0x61, 0x6e, 0x79, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x2b, 0x5a, 0x29,
|
||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e,
|
||||
0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, 0x70, 0x65,
|
||||
0x73, 0x2f, 0x61, 0x6e, 0x79, 0x3b, 0x61, 0x6e, 0x79, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var file_github_com_golang_protobuf_ptypes_any_any_proto_goTypes = []interface{}{}
|
||||
var file_github_com_golang_protobuf_ptypes_any_any_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_github_com_golang_protobuf_ptypes_any_any_proto_init() }
|
||||
func file_github_com_golang_protobuf_ptypes_any_any_proto_init() {
|
||||
if File_github_com_golang_protobuf_ptypes_any_any_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_github_com_golang_protobuf_ptypes_any_any_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 0,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_github_com_golang_protobuf_ptypes_any_any_proto_goTypes,
|
||||
DependencyIndexes: file_github_com_golang_protobuf_ptypes_any_any_proto_depIdxs,
|
||||
}.Build()
|
||||
File_github_com_golang_protobuf_ptypes_any_any_proto = out.File
|
||||
file_github_com_golang_protobuf_ptypes_any_any_proto_rawDesc = nil
|
||||
file_github_com_golang_protobuf_ptypes_any_any_proto_goTypes = nil
|
||||
file_github_com_golang_protobuf_ptypes_any_any_proto_depIdxs = nil
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ptypes provides functionality for interacting with well-known types.
|
||||
//
|
||||
// Deprecated: Well-known types have specialized functionality directly
|
||||
// injected into the generated packages for each message type.
|
||||
// See the deprecation notice for each function for the suggested alternative.
|
||||
package ptypes
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ptypes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
durationpb "github.com/golang/protobuf/ptypes/duration"
|
||||
)
|
||||
|
||||
// Range of google.protobuf.Duration as specified in duration.proto.
|
||||
// This is about 10,000 years in seconds.
|
||||
const (
|
||||
maxSeconds = int64(10000 * 365.25 * 24 * 60 * 60)
|
||||
minSeconds = -maxSeconds
|
||||
)
|
||||
|
||||
// Duration converts a durationpb.Duration to a time.Duration.
|
||||
// Duration returns an error if dur is invalid or overflows a time.Duration.
|
||||
//
|
||||
// Deprecated: Call the dur.AsDuration and dur.CheckValid methods instead.
|
||||
func Duration(dur *durationpb.Duration) (time.Duration, error) {
|
||||
if err := validateDuration(dur); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
d := time.Duration(dur.Seconds) * time.Second
|
||||
if int64(d/time.Second) != dur.Seconds {
|
||||
return 0, fmt.Errorf("duration: %v is out of range for time.Duration", dur)
|
||||
}
|
||||
if dur.Nanos != 0 {
|
||||
d += time.Duration(dur.Nanos) * time.Nanosecond
|
||||
if (d < 0) != (dur.Nanos < 0) {
|
||||
return 0, fmt.Errorf("duration: %v is out of range for time.Duration", dur)
|
||||
}
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// DurationProto converts a time.Duration to a durationpb.Duration.
|
||||
//
|
||||
// Deprecated: Call the durationpb.New function instead.
|
||||
func DurationProto(d time.Duration) *durationpb.Duration {
|
||||
nanos := d.Nanoseconds()
|
||||
secs := nanos / 1e9
|
||||
nanos -= secs * 1e9
|
||||
return &durationpb.Duration{
|
||||
Seconds: int64(secs),
|
||||
Nanos: int32(nanos),
|
||||
}
|
||||
}
|
||||
|
||||
// validateDuration determines whether the durationpb.Duration is valid
|
||||
// according to the definition in google/protobuf/duration.proto.
|
||||
// A valid durpb.Duration may still be too large to fit into a time.Duration
|
||||
// Note that the range of durationpb.Duration is about 10,000 years,
|
||||
// while the range of time.Duration is about 290 years.
|
||||
func validateDuration(dur *durationpb.Duration) error {
|
||||
if dur == nil {
|
||||
return errors.New("duration: nil Duration")
|
||||
}
|
||||
if dur.Seconds < minSeconds || dur.Seconds > maxSeconds {
|
||||
return fmt.Errorf("duration: %v: seconds out of range", dur)
|
||||
}
|
||||
if dur.Nanos <= -1e9 || dur.Nanos >= 1e9 {
|
||||
return fmt.Errorf("duration: %v: nanos out of range", dur)
|
||||
}
|
||||
// Seconds and Nanos must have the same sign, unless d.Nanos is zero.
|
||||
if (dur.Seconds < 0 && dur.Nanos > 0) || (dur.Seconds > 0 && dur.Nanos < 0) {
|
||||
return fmt.Errorf("duration: %v: seconds and nanos have different signs", dur)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: github.com/golang/protobuf/ptypes/duration/duration.proto
|
||||
|
||||
package duration
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
durationpb "google.golang.org/protobuf/types/known/durationpb"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// Symbols defined in public import of google/protobuf/duration.proto.
|
||||
|
||||
type Duration = durationpb.Duration
|
||||
|
||||
var File_github_com_golang_protobuf_ptypes_duration_duration_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_github_com_golang_protobuf_ptypes_duration_duration_proto_rawDesc = []byte{
|
||||
0x0a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c,
|
||||
0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79,
|
||||
0x70, 0x65, 0x73, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x64, 0x75, 0x72,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x35, 0x5a, 0x33, 0x67,
|
||||
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67,
|
||||
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, 0x70, 0x65, 0x73,
|
||||
0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var file_github_com_golang_protobuf_ptypes_duration_duration_proto_goTypes = []interface{}{}
|
||||
var file_github_com_golang_protobuf_ptypes_duration_duration_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_github_com_golang_protobuf_ptypes_duration_duration_proto_init() }
|
||||
func file_github_com_golang_protobuf_ptypes_duration_duration_proto_init() {
|
||||
if File_github_com_golang_protobuf_ptypes_duration_duration_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_github_com_golang_protobuf_ptypes_duration_duration_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 0,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_github_com_golang_protobuf_ptypes_duration_duration_proto_goTypes,
|
||||
DependencyIndexes: file_github_com_golang_protobuf_ptypes_duration_duration_proto_depIdxs,
|
||||
}.Build()
|
||||
File_github_com_golang_protobuf_ptypes_duration_duration_proto = out.File
|
||||
file_github_com_golang_protobuf_ptypes_duration_duration_proto_rawDesc = nil
|
||||
file_github_com_golang_protobuf_ptypes_duration_duration_proto_goTypes = nil
|
||||
file_github_com_golang_protobuf_ptypes_duration_duration_proto_depIdxs = nil
|
||||
}
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ptypes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
timestamppb "github.com/golang/protobuf/ptypes/timestamp"
|
||||
)
|
||||
|
||||
// Range of google.protobuf.Duration as specified in timestamp.proto.
|
||||
const (
|
||||
// Seconds field of the earliest valid Timestamp.
|
||||
// This is time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
|
||||
minValidSeconds = -62135596800
|
||||
// Seconds field just after the latest valid Timestamp.
|
||||
// This is time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
|
||||
maxValidSeconds = 253402300800
|
||||
)
|
||||
|
||||
// Timestamp converts a timestamppb.Timestamp to a time.Time.
|
||||
// It returns an error if the argument is invalid.
|
||||
//
|
||||
// Unlike most Go functions, if Timestamp returns an error, the first return
|
||||
// value is not the zero time.Time. Instead, it is the value obtained from the
|
||||
// time.Unix function when passed the contents of the Timestamp, in the UTC
|
||||
// locale. This may or may not be a meaningful time; many invalid Timestamps
|
||||
// do map to valid time.Times.
|
||||
//
|
||||
// A nil Timestamp returns an error. The first return value in that case is
|
||||
// undefined.
|
||||
//
|
||||
// Deprecated: Call the ts.AsTime and ts.CheckValid methods instead.
|
||||
func Timestamp(ts *timestamppb.Timestamp) (time.Time, error) {
|
||||
// Don't return the zero value on error, because corresponds to a valid
|
||||
// timestamp. Instead return whatever time.Unix gives us.
|
||||
var t time.Time
|
||||
if ts == nil {
|
||||
t = time.Unix(0, 0).UTC() // treat nil like the empty Timestamp
|
||||
} else {
|
||||
t = time.Unix(ts.Seconds, int64(ts.Nanos)).UTC()
|
||||
}
|
||||
return t, validateTimestamp(ts)
|
||||
}
|
||||
|
||||
// TimestampNow returns a google.protobuf.Timestamp for the current time.
|
||||
//
|
||||
// Deprecated: Call the timestamppb.Now function instead.
|
||||
func TimestampNow() *timestamppb.Timestamp {
|
||||
ts, err := TimestampProto(time.Now())
|
||||
if err != nil {
|
||||
panic("ptypes: time.Now() out of Timestamp range")
|
||||
}
|
||||
return ts
|
||||
}
|
||||
|
||||
// TimestampProto converts the time.Time to a google.protobuf.Timestamp proto.
|
||||
// It returns an error if the resulting Timestamp is invalid.
|
||||
//
|
||||
// Deprecated: Call the timestamppb.New function instead.
|
||||
func TimestampProto(t time.Time) (*timestamppb.Timestamp, error) {
|
||||
ts := ×tamppb.Timestamp{
|
||||
Seconds: t.Unix(),
|
||||
Nanos: int32(t.Nanosecond()),
|
||||
}
|
||||
if err := validateTimestamp(ts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
// TimestampString returns the RFC 3339 string for valid Timestamps.
|
||||
// For invalid Timestamps, it returns an error message in parentheses.
|
||||
//
|
||||
// Deprecated: Call the ts.AsTime method instead,
|
||||
// followed by a call to the Format method on the time.Time value.
|
||||
func TimestampString(ts *timestamppb.Timestamp) string {
|
||||
t, err := Timestamp(ts)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("(%v)", err)
|
||||
}
|
||||
return t.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// validateTimestamp determines whether a Timestamp is valid.
|
||||
// A valid timestamp represents a time in the range [0001-01-01, 10000-01-01)
|
||||
// and has a Nanos field in the range [0, 1e9).
|
||||
//
|
||||
// If the Timestamp is valid, validateTimestamp returns nil.
|
||||
// Otherwise, it returns an error that describes the problem.
|
||||
//
|
||||
// Every valid Timestamp can be represented by a time.Time,
|
||||
// but the converse is not true.
|
||||
func validateTimestamp(ts *timestamppb.Timestamp) error {
|
||||
if ts == nil {
|
||||
return errors.New("timestamp: nil Timestamp")
|
||||
}
|
||||
if ts.Seconds < minValidSeconds {
|
||||
return fmt.Errorf("timestamp: %v before 0001-01-01", ts)
|
||||
}
|
||||
if ts.Seconds >= maxValidSeconds {
|
||||
return fmt.Errorf("timestamp: %v after 10000-01-01", ts)
|
||||
}
|
||||
if ts.Nanos < 0 || ts.Nanos >= 1e9 {
|
||||
return fmt.Errorf("timestamp: %v: nanos not in range [0, 1e9)", ts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user