TUN-3019: Remove declarative tunnel entry code
This commit is contained in:
parent
be0514c5c9
commit
fb82b2ced5
|
@ -21,7 +21,6 @@ import (
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
|
||||||
"github.com/cloudflare/cloudflared/connection"
|
|
||||||
"github.com/cloudflare/cloudflared/dbconnect"
|
"github.com/cloudflare/cloudflared/dbconnect"
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
"github.com/cloudflare/cloudflared/h2mux"
|
||||||
"github.com/cloudflare/cloudflared/hello"
|
"github.com/cloudflare/cloudflared/hello"
|
||||||
|
@ -32,10 +31,8 @@ import (
|
||||||
"github.com/cloudflare/cloudflared/socks"
|
"github.com/cloudflare/cloudflared/socks"
|
||||||
"github.com/cloudflare/cloudflared/sshlog"
|
"github.com/cloudflare/cloudflared/sshlog"
|
||||||
"github.com/cloudflare/cloudflared/sshserver"
|
"github.com/cloudflare/cloudflared/sshserver"
|
||||||
"github.com/cloudflare/cloudflared/supervisor"
|
|
||||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||||
"github.com/cloudflare/cloudflared/tunneldns"
|
"github.com/cloudflare/cloudflared/tunneldns"
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
|
||||||
"github.com/cloudflare/cloudflared/websocket"
|
"github.com/cloudflare/cloudflared/websocket"
|
||||||
|
|
||||||
"github.com/coreos/go-systemd/daemon"
|
"github.com/coreos/go-systemd/daemon"
|
||||||
|
@ -93,8 +90,6 @@ const (
|
||||||
// bastionFlag is to enable bastion, or jump host, operation
|
// bastionFlag is to enable bastion, or jump host, operation
|
||||||
bastionFlag = "bastion"
|
bastionFlag = "bastion"
|
||||||
|
|
||||||
noIntentMsg = "The --intent argument is required. Cloudflared looks up an Intent to determine what configuration to use (i.e. which tunnels to start). If you don't have any Intents yet, you can use a placeholder Intent Label for now. Then, when you make an Intent with that label, cloudflared will get notified and open the tunnels you specified in that Intent."
|
|
||||||
|
|
||||||
debugLevelWarning = "At debug level, request URL, method, protocol, content legnth and header will be logged. " +
|
debugLevelWarning = "At debug level, request URL, method, protocol, content legnth and header will be logged. " +
|
||||||
"Response status, content length and header will also be logged in debug level."
|
"Response status, content length and header will also be logged in debug level."
|
||||||
)
|
)
|
||||||
|
@ -322,10 +317,6 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
||||||
cancel()
|
cancel()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if c.IsSet("use-declarative-tunnels") {
|
|
||||||
return startDeclarativeTunnel(ctx, c, cloudflaredID, buildInfo, &listeners, logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update needs to be after DNS proxy is up to resolve equinox server address
|
// update needs to be after DNS proxy is up to resolve equinox server address
|
||||||
if updater.IsAutoupdateEnabled(c, logger) {
|
if updater.IsAutoupdateEnabled(c, logger) {
|
||||||
logger.Infof("Autoupdate frequency is set to %v", c.Duration("autoupdate-freq"))
|
logger.Infof("Autoupdate frequency is set to %v", c.Duration("autoupdate-freq"))
|
||||||
|
@ -500,124 +491,6 @@ func isProxyDestinationConfigured(staticHost string, c *cli.Context) bool {
|
||||||
return staticHost != "" || c.IsSet(bastionFlag)
|
return staticHost != "" || c.IsSet(bastionFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startDeclarativeTunnel(ctx context.Context,
|
|
||||||
c *cli.Context,
|
|
||||||
cloudflaredID uuid.UUID,
|
|
||||||
buildInfo *buildinfo.BuildInfo,
|
|
||||||
listeners *gracenet.Net,
|
|
||||||
logger logger.Service,
|
|
||||||
) error {
|
|
||||||
reverseProxyOrigin, err := defaultOriginConfig(c)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("%s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
reverseProxyConfig, err := pogs.NewReverseProxyConfig(
|
|
||||||
c.String("hostname"),
|
|
||||||
reverseProxyOrigin,
|
|
||||||
c.Uint64("retries"),
|
|
||||||
c.Duration("proxy-connection-timeout"),
|
|
||||||
c.Uint64("compression-quality"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("Cannot initialize default client config because reverse proxy config is invalid: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defaultClientConfig := &pogs.ClientConfig{
|
|
||||||
Version: pogs.InitVersion(),
|
|
||||||
SupervisorConfig: &pogs.SupervisorConfig{
|
|
||||||
AutoUpdateFrequency: c.Duration("autoupdate-freq"),
|
|
||||||
MetricsUpdateFrequency: c.Duration("metrics-update-freq"),
|
|
||||||
GracePeriod: c.Duration("grace-period"),
|
|
||||||
},
|
|
||||||
EdgeConnectionConfig: &pogs.EdgeConnectionConfig{
|
|
||||||
NumHAConnections: uint8(c.Int("ha-connections")),
|
|
||||||
HeartbeatInterval: c.Duration("heartbeat-interval"),
|
|
||||||
Timeout: c.Duration("dial-edge-timeout"),
|
|
||||||
MaxFailedHeartbeats: c.Uint64("heartbeat-count"),
|
|
||||||
},
|
|
||||||
DoHProxyConfigs: []*pogs.DoHProxyConfig{},
|
|
||||||
ReverseProxyConfigs: []*pogs.ReverseProxyConfig{reverseProxyConfig},
|
|
||||||
}
|
|
||||||
|
|
||||||
autoupdater := updater.NewAutoUpdater(defaultClientConfig.SupervisorConfig.AutoUpdateFrequency, listeners, logger)
|
|
||||||
|
|
||||||
originCert, err := getOriginCert(c, logger)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("error getting origin cert: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
toEdgeTLSConfig, err := tlsconfig.CreateTunnelConfig(c)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("unable to create TLS config to connect with edge: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tags, err := NewTagSliceFromCLI(c.StringSlice("tag"))
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("unable to parse tag: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
intentLabel := c.String("intent")
|
|
||||||
if intentLabel == "" {
|
|
||||||
logger.Error("--intent was empty")
|
|
||||||
return fmt.Errorf(noIntentMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
cloudflaredConfig := &connection.CloudflaredConfig{
|
|
||||||
BuildInfo: buildInfo,
|
|
||||||
CloudflaredID: cloudflaredID,
|
|
||||||
IntentLabel: intentLabel,
|
|
||||||
Tags: tags,
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceDiscoverer, err := serviceDiscoverer(c, logger)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("unable to create service discoverer: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
supervisor, err := supervisor.NewSupervisor(defaultClientConfig, originCert, toEdgeTLSConfig,
|
|
||||||
serviceDiscoverer, cloudflaredConfig, autoupdater, updater.SupportAutoUpdate(logger), logger)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("unable to create Supervisor: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return supervisor.Run(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultOriginConfig(c *cli.Context) (pogs.OriginConfig, error) {
|
|
||||||
if c.IsSet("hello-world") {
|
|
||||||
return &pogs.HelloWorldOriginConfig{}, nil
|
|
||||||
}
|
|
||||||
originConfig := &pogs.HTTPOriginConfig{
|
|
||||||
TCPKeepAlive: c.Duration("proxy-tcp-keepalive"),
|
|
||||||
DialDualStack: !c.Bool("proxy-no-happy-eyeballs"),
|
|
||||||
TLSHandshakeTimeout: c.Duration("proxy-tls-timeout"),
|
|
||||||
TLSVerify: !c.Bool("no-tls-verify"),
|
|
||||||
OriginCAPool: c.String("origin-ca-pool"),
|
|
||||||
OriginServerName: c.String("origin-server-name"),
|
|
||||||
MaxIdleConnections: c.Uint64("proxy-keepalive-connections"),
|
|
||||||
IdleConnectionTimeout: c.Duration("proxy-keepalive-timeout"),
|
|
||||||
ProxyConnectionTimeout: c.Duration("proxy-connection-timeout"),
|
|
||||||
ExpectContinueTimeout: c.Duration("proxy-expect-continue-timeout"),
|
|
||||||
ChunkedEncoding: c.Bool("no-chunked-encoding"),
|
|
||||||
}
|
|
||||||
if c.IsSet("unix-socket") {
|
|
||||||
unixSocket, err := config.ValidateUnixSocket(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "error validating --unix-socket")
|
|
||||||
}
|
|
||||||
originConfig.URLString = unixSocket
|
|
||||||
}
|
|
||||||
originAddr, err := config.ValidateUrl(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "error validating origin URL")
|
|
||||||
}
|
|
||||||
originConfig.URLString = originAddr
|
|
||||||
return originConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitToShutdown(wg *sync.WaitGroup,
|
func waitToShutdown(wg *sync.WaitGroup,
|
||||||
errC chan error,
|
errC chan error,
|
||||||
shutdownC, graceShutdownC chan struct{},
|
shutdownC, graceShutdownC chan struct{},
|
||||||
|
@ -1064,18 +937,6 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||||
EnvVars: []string{"TUNNEL_TRACE_OUTPUT"},
|
EnvVars: []string{"TUNNEL_TRACE_OUTPUT"},
|
||||||
Hidden: shouldHide,
|
Hidden: shouldHide,
|
||||||
}),
|
}),
|
||||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
|
||||||
Name: "use-declarative-tunnels",
|
|
||||||
Usage: "Test establishing connections with declarative tunnel methods.",
|
|
||||||
EnvVars: []string{"TUNNEL_USE_DECLARATIVE"},
|
|
||||||
Hidden: true,
|
|
||||||
}),
|
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{
|
|
||||||
Name: "intent",
|
|
||||||
Usage: "The label of an Intent from which `cloudflared` should gets its tunnels from. Intents can be created in the Origin Registry UI.",
|
|
||||||
EnvVars: []string{"TUNNEL_INTENT"},
|
|
||||||
Hidden: true,
|
|
||||||
}),
|
|
||||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||||
Name: "use-reconnect-token",
|
Name: "use-reconnect-token",
|
||||||
Usage: "Test reestablishing connections with the new 'reconnect token' flow.",
|
Usage: "Test reestablishing connections with the new 'reconnect token' flow.",
|
||||||
|
|
|
@ -14,7 +14,6 @@ import (
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||||
"github.com/cloudflare/cloudflared/edgediscovery"
|
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
"github.com/cloudflare/cloudflared/logger"
|
||||||
"github.com/cloudflare/cloudflared/origin"
|
"github.com/cloudflare/cloudflared/origin"
|
||||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||||
|
@ -290,15 +289,6 @@ func prepareTunnelConfig(
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func serviceDiscoverer(c *cli.Context, logger logger.Service) (*edgediscovery.Edge, error) {
|
|
||||||
// If --edge is specfied, resolve edge server addresses
|
|
||||||
if len(c.StringSlice("edge")) > 0 {
|
|
||||||
return edgediscovery.StaticEdge(logger, c.StringSlice("edge"))
|
|
||||||
}
|
|
||||||
// Otherwise lookup edge server addresses through service discovery
|
|
||||||
return edgediscovery.ResolveEdge(logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isRunningFromTerminal() bool {
|
func isRunningFromTerminal() bool {
|
||||||
return terminal.IsTerminal(int(os.Stdout.Fd()))
|
return terminal.IsTerminal(int(os.Stdout.Fd()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
package connection
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
|
||||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
openStreamTimeout = 30 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
type Connection struct {
|
|
||||||
id uuid.UUID
|
|
||||||
muxer *h2mux.Muxer
|
|
||||||
addr *net.TCPAddr
|
|
||||||
isLongLived bool
|
|
||||||
longLivedID int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConnection(muxer *h2mux.Muxer, addr *net.TCPAddr) (*Connection, error) {
|
|
||||||
id, err := uuid.NewRandom()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Connection{
|
|
||||||
id: id,
|
|
||||||
muxer: muxer,
|
|
||||||
addr: addr,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Connection) Serve(ctx context.Context) error {
|
|
||||||
// Serve doesn't return until h2mux is shutdown
|
|
||||||
return c.muxer.Serve(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect is used to establish connections with cloudflare's edge network
|
|
||||||
func (c *Connection) Connect(ctx context.Context, parameters *tunnelpogs.ConnectParameters, logger logger.Service) (tunnelpogs.ConnectResult, error) {
|
|
||||||
tsClient, err := NewRPCClient(ctx, c.muxer, logger, openStreamTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot create new RPC connection")
|
|
||||||
}
|
|
||||||
defer tsClient.Close()
|
|
||||||
return tsClient.Connect(ctx, parameters)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Connection) Shutdown() {
|
|
||||||
c.muxer.Shutdown()
|
|
||||||
}
|
|
|
@ -1,302 +0,0 @@
|
||||||
package connection
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
|
|
||||||
"github.com/cloudflare/cloudflared/edgediscovery"
|
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
|
||||||
"github.com/cloudflare/cloudflared/streamhandler"
|
|
||||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
quickStartLink = "https://developers.cloudflare.com/argo-tunnel/quickstart/"
|
|
||||||
faqLink = "https://developers.cloudflare.com/argo-tunnel/faq/"
|
|
||||||
defaultRetryAfter = time.Second * 5
|
|
||||||
packageNamespace = "connection"
|
|
||||||
edgeManagerSubsystem = "edgemanager"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EdgeManager manages connections with the edge
|
|
||||||
type EdgeManager struct {
|
|
||||||
// streamHandler handles stream opened by the edge
|
|
||||||
streamHandler *streamhandler.StreamHandler
|
|
||||||
// TLSConfig is the TLS configuration to connect with edge
|
|
||||||
tlsConfig *tls.Config
|
|
||||||
// cloudflaredConfig is the cloudflared configuration that is determined when the process first starts
|
|
||||||
cloudflaredConfig *CloudflaredConfig
|
|
||||||
// serviceDiscoverer returns the next edge addr to connect to
|
|
||||||
serviceDiscoverer *edgediscovery.Edge
|
|
||||||
// state is attributes of ConnectionManager that can change during runtime.
|
|
||||||
state *edgeManagerState
|
|
||||||
|
|
||||||
logger logger.Service
|
|
||||||
|
|
||||||
metrics *metrics
|
|
||||||
}
|
|
||||||
|
|
||||||
type metrics struct {
|
|
||||||
// activeStreams is a gauge shared by all muxers of this process to expose the total number of active streams
|
|
||||||
activeStreams prometheus.Gauge
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMetrics(namespace, subsystem string) *metrics {
|
|
||||||
return &metrics{
|
|
||||||
activeStreams: h2mux.NewActiveStreamsMetrics(namespace, subsystem),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EdgeManagerConfigurable is the configurable attributes of a EdgeConnectionManager
|
|
||||||
type EdgeManagerConfigurable struct {
|
|
||||||
TunnelHostnames []h2mux.TunnelHostname
|
|
||||||
*tunnelpogs.EdgeConnectionConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloudflaredConfig struct {
|
|
||||||
CloudflaredID uuid.UUID
|
|
||||||
Tags []tunnelpogs.Tag
|
|
||||||
BuildInfo *buildinfo.BuildInfo
|
|
||||||
IntentLabel string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEdgeManager(
|
|
||||||
streamHandler *streamhandler.StreamHandler,
|
|
||||||
edgeConnMgrConfigurable *EdgeManagerConfigurable,
|
|
||||||
userCredential []byte,
|
|
||||||
tlsConfig *tls.Config,
|
|
||||||
serviceDiscoverer *edgediscovery.Edge,
|
|
||||||
cloudflaredConfig *CloudflaredConfig,
|
|
||||||
logger logger.Service,
|
|
||||||
) *EdgeManager {
|
|
||||||
return &EdgeManager{
|
|
||||||
streamHandler: streamHandler,
|
|
||||||
tlsConfig: tlsConfig,
|
|
||||||
cloudflaredConfig: cloudflaredConfig,
|
|
||||||
serviceDiscoverer: serviceDiscoverer,
|
|
||||||
state: newEdgeConnectionManagerState(edgeConnMgrConfigurable, userCredential),
|
|
||||||
logger: logger,
|
|
||||||
metrics: newMetrics(packageNamespace, edgeManagerSubsystem),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EdgeManager) Run(ctx context.Context) error {
|
|
||||||
defer em.shutdown()
|
|
||||||
|
|
||||||
// Currently, declarative tunnels don't have any concept of a stable connection
|
|
||||||
// Each edge connection is transient and when it dies, it is replaced by a different one,
|
|
||||||
// not restarted.
|
|
||||||
// So in the future we should really change this so that n connections are stored individually
|
|
||||||
connIndex := 0
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return errors.Wrap(ctx.Err(), "EdgeConnectionManager terminated")
|
|
||||||
default:
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
// Create/delete connection one at a time, so we don't need to adjust for connections that are being created/deleted
|
|
||||||
// in shouldCreateConnection or shouldReduceConnection calculation
|
|
||||||
if em.state.shouldCreateConnection(em.serviceDiscoverer.AvailableAddrs()) {
|
|
||||||
if connErr := em.newConnection(ctx, connIndex); connErr != nil {
|
|
||||||
if !connErr.ShouldRetry {
|
|
||||||
em.logger.Errorf("connectionManager: %s with error: %s", em.noRetryMessage(), connErr)
|
|
||||||
return connErr
|
|
||||||
}
|
|
||||||
em.logger.Errorf("connectionManager: cannot create new connection: %s", connErr)
|
|
||||||
} else {
|
|
||||||
connIndex++
|
|
||||||
}
|
|
||||||
} else if em.state.shouldReduceConnection() {
|
|
||||||
if err := em.closeConnection(ctx); err != nil {
|
|
||||||
em.logger.Errorf("connectionManager: cannot close connection: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EdgeManager) UpdateConfigurable(newConfigurable *EdgeManagerConfigurable) {
|
|
||||||
em.logger.Infof("New edge connection manager configuration %+v", newConfigurable)
|
|
||||||
em.state.updateConfigurable(newConfigurable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EdgeManager) newConnection(ctx context.Context, index int) *tunnelpogs.ConnectError {
|
|
||||||
edgeTCPAddr, err := em.serviceDiscoverer.GetAddr(index)
|
|
||||||
if err != nil {
|
|
||||||
return retryConnection(fmt.Sprintf("edge address discovery error: %v", err))
|
|
||||||
}
|
|
||||||
configurable := em.state.getConfigurable()
|
|
||||||
edgeConn, err := DialEdge(ctx, configurable.Timeout, em.tlsConfig, edgeTCPAddr)
|
|
||||||
if err != nil {
|
|
||||||
return retryConnection(fmt.Sprintf("dial edge error: %v", err))
|
|
||||||
}
|
|
||||||
// Establish a muxed connection with the edge
|
|
||||||
// Client mux handshake with agent server
|
|
||||||
muxer, err := h2mux.Handshake(edgeConn, edgeConn, h2mux.MuxerConfig{
|
|
||||||
Timeout: configurable.Timeout,
|
|
||||||
Handler: em.streamHandler,
|
|
||||||
IsClient: true,
|
|
||||||
HeartbeatInterval: configurable.HeartbeatInterval,
|
|
||||||
MaxHeartbeats: configurable.MaxFailedHeartbeats,
|
|
||||||
Logger: em.logger,
|
|
||||||
}, em.metrics.activeStreams)
|
|
||||||
if err != nil {
|
|
||||||
retryConnection(fmt.Sprintf("couldn't perform handshake with edge: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
h2muxConn, err := newConnection(muxer, edgeTCPAddr)
|
|
||||||
if err != nil {
|
|
||||||
return retryConnection(fmt.Sprintf("couldn't create h2mux connection: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
go em.serveConn(ctx, h2muxConn)
|
|
||||||
|
|
||||||
connResult, err := h2muxConn.Connect(ctx, &tunnelpogs.ConnectParameters{
|
|
||||||
CloudflaredID: em.cloudflaredConfig.CloudflaredID,
|
|
||||||
CloudflaredVersion: em.cloudflaredConfig.BuildInfo.CloudflaredVersion,
|
|
||||||
NumPreviousAttempts: 0,
|
|
||||||
OriginCert: em.state.getUserCredential(),
|
|
||||||
IntentLabel: em.cloudflaredConfig.IntentLabel,
|
|
||||||
Tags: em.cloudflaredConfig.Tags,
|
|
||||||
}, em.logger)
|
|
||||||
if err != nil {
|
|
||||||
h2muxConn.Shutdown()
|
|
||||||
return retryConnection(fmt.Sprintf("couldn't connect to edge: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if connErr := connResult.ConnectError(); connErr != nil {
|
|
||||||
return connErr
|
|
||||||
}
|
|
||||||
|
|
||||||
em.state.newConnection(h2muxConn)
|
|
||||||
em.logger.Infof("connectionManager: connected to %s", connResult.ConnectedTo())
|
|
||||||
|
|
||||||
if connResult.ClientConfig() != nil {
|
|
||||||
em.streamHandler.UseConfiguration(ctx, connResult.ClientConfig())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EdgeManager) closeConnection(ctx context.Context) error {
|
|
||||||
conn := em.state.getFirstConnection()
|
|
||||||
if conn == nil {
|
|
||||||
return fmt.Errorf("no connection to close")
|
|
||||||
}
|
|
||||||
conn.Shutdown()
|
|
||||||
// teardown will be handled by EdgeManager.serveConn in another goroutine
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EdgeManager) serveConn(ctx context.Context, conn *Connection) {
|
|
||||||
err := conn.Serve(ctx)
|
|
||||||
em.logger.Errorf("connectionManager: Connection closed: %s", err)
|
|
||||||
em.state.closeConnection(conn)
|
|
||||||
em.serviceDiscoverer.GiveBack(conn.addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EdgeManager) noRetryMessage() string {
|
|
||||||
messageTemplate := "cloudflared could not register an Argo Tunnel on your account. Please confirm the following before trying again:" +
|
|
||||||
"1. You have Argo Smart Routing enabled in your account, See Enable Argo section of %s." +
|
|
||||||
"2. Your credential at %s is still valid. See %s."
|
|
||||||
return fmt.Sprintf(messageTemplate, quickStartLink, em.state.getConfigurable().UserCredentialPath, faqLink)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EdgeManager) shutdown() {
|
|
||||||
em.state.shutdown()
|
|
||||||
}
|
|
||||||
|
|
||||||
type edgeManagerState struct {
|
|
||||||
sync.RWMutex
|
|
||||||
configurable *EdgeManagerConfigurable
|
|
||||||
userCredential []byte
|
|
||||||
conns map[uuid.UUID]*Connection
|
|
||||||
}
|
|
||||||
|
|
||||||
func newEdgeConnectionManagerState(configurable *EdgeManagerConfigurable, userCredential []byte) *edgeManagerState {
|
|
||||||
return &edgeManagerState{
|
|
||||||
configurable: configurable,
|
|
||||||
userCredential: userCredential,
|
|
||||||
conns: make(map[uuid.UUID]*Connection),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ems *edgeManagerState) shouldCreateConnection(availableEdgeAddrs int) bool {
|
|
||||||
ems.RLock()
|
|
||||||
defer ems.RUnlock()
|
|
||||||
expectedHAConns := int(ems.configurable.NumHAConnections)
|
|
||||||
if availableEdgeAddrs < expectedHAConns {
|
|
||||||
expectedHAConns = availableEdgeAddrs
|
|
||||||
}
|
|
||||||
return len(ems.conns) < expectedHAConns
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ems *edgeManagerState) shouldReduceConnection() bool {
|
|
||||||
ems.RLock()
|
|
||||||
defer ems.RUnlock()
|
|
||||||
return uint8(len(ems.conns)) > ems.configurable.NumHAConnections
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ems *edgeManagerState) newConnection(conn *Connection) {
|
|
||||||
ems.Lock()
|
|
||||||
defer ems.Unlock()
|
|
||||||
ems.conns[conn.id] = conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ems *edgeManagerState) closeConnection(conn *Connection) {
|
|
||||||
ems.Lock()
|
|
||||||
defer ems.Unlock()
|
|
||||||
delete(ems.conns, conn.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ems *edgeManagerState) getFirstConnection() *Connection {
|
|
||||||
ems.RLock()
|
|
||||||
defer ems.RUnlock()
|
|
||||||
|
|
||||||
for _, conn := range ems.conns {
|
|
||||||
return conn
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ems *edgeManagerState) shutdown() {
|
|
||||||
ems.Lock()
|
|
||||||
defer ems.Unlock()
|
|
||||||
for _, conn := range ems.conns {
|
|
||||||
conn.Shutdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ems *edgeManagerState) getConfigurable() *EdgeManagerConfigurable {
|
|
||||||
ems.Lock()
|
|
||||||
defer ems.Unlock()
|
|
||||||
return ems.configurable
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ems *edgeManagerState) updateConfigurable(newConfigurable *EdgeManagerConfigurable) {
|
|
||||||
ems.Lock()
|
|
||||||
defer ems.Unlock()
|
|
||||||
ems.configurable = newConfigurable
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ems *edgeManagerState) getUserCredential() []byte {
|
|
||||||
ems.RLock()
|
|
||||||
defer ems.RUnlock()
|
|
||||||
return ems.userCredential
|
|
||||||
}
|
|
||||||
|
|
||||||
func retryConnection(cause string) *tunnelpogs.ConnectError {
|
|
||||||
return &tunnelpogs.ConnectError{
|
|
||||||
Cause: cause,
|
|
||||||
RetryAfter: defaultRetryAfter,
|
|
||||||
ShouldRetry: true,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
package connection
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
|
|
||||||
"github.com/cloudflare/cloudflared/edgediscovery"
|
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
|
||||||
"github.com/cloudflare/cloudflared/streamhandler"
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
configurable = &EdgeManagerConfigurable{
|
|
||||||
[]h2mux.TunnelHostname{
|
|
||||||
"http.example.com",
|
|
||||||
"ws.example.com",
|
|
||||||
"hello.example.com",
|
|
||||||
},
|
|
||||||
&pogs.EdgeConnectionConfig{
|
|
||||||
NumHAConnections: 1,
|
|
||||||
HeartbeatInterval: 1 * time.Second,
|
|
||||||
Timeout: 5 * time.Second,
|
|
||||||
MaxFailedHeartbeats: 3,
|
|
||||||
UserCredentialPath: "/etc/cloudflared/cert.pem",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cloudflaredConfig = &CloudflaredConfig{
|
|
||||||
CloudflaredID: uuid.New(),
|
|
||||||
Tags: []pogs.Tag{
|
|
||||||
{Name: "pool", Value: "east-6"},
|
|
||||||
},
|
|
||||||
BuildInfo: &buildinfo.BuildInfo{
|
|
||||||
GoOS: "linux",
|
|
||||||
GoVersion: "1.12",
|
|
||||||
GoArch: "amd64",
|
|
||||||
CloudflaredVersion: "2019.6.0",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func mockEdgeManager() *EdgeManager {
|
|
||||||
newConfigChan := make(chan<- *pogs.ClientConfig)
|
|
||||||
useConfigResultChan := make(<-chan *pogs.UseConfigurationResult)
|
|
||||||
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
|
|
||||||
edge := edgediscovery.MockEdge(logger, []*net.TCPAddr{})
|
|
||||||
return NewEdgeManager(
|
|
||||||
streamhandler.NewStreamHandler(newConfigChan, useConfigResultChan, logger),
|
|
||||||
configurable,
|
|
||||||
[]byte{},
|
|
||||||
nil,
|
|
||||||
edge,
|
|
||||||
cloudflaredConfig,
|
|
||||||
logger,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateConfigurable(t *testing.T) {
|
|
||||||
m := mockEdgeManager()
|
|
||||||
newConfigurable := &EdgeManagerConfigurable{
|
|
||||||
[]h2mux.TunnelHostname{
|
|
||||||
"second.example.com",
|
|
||||||
},
|
|
||||||
&pogs.EdgeConnectionConfig{
|
|
||||||
NumHAConnections: 2,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
m.UpdateConfigurable(newConfigurable)
|
|
||||||
|
|
||||||
assert.Equal(t, newConfigurable, m.state.getConfigurable())
|
|
||||||
}
|
|
2
go.mod
2
go.mod
|
@ -70,7 +70,7 @@ require (
|
||||||
gopkg.in/square/go-jose.v2 v2.4.0 // indirect
|
gopkg.in/square/go-jose.v2 v2.4.0 // indirect
|
||||||
gopkg.in/urfave/cli.v2 v2.0.0-20180128181224-d604b6ffeee8
|
gopkg.in/urfave/cli.v2 v2.0.0-20180128181224-d604b6ffeee8
|
||||||
gopkg.in/yaml.v2 v2.2.4
|
gopkg.in/yaml.v2 v2.2.4
|
||||||
zombiezen.com/go/capnproto2 v0.0.0-20180616160808-7cfd211c19c7
|
zombiezen.com/go/capnproto2 v2.18.0+incompatible
|
||||||
)
|
)
|
||||||
|
|
||||||
// ../../go/pkg/mod/github.com/coredns/coredns@v1.2.0/plugin/metrics/metrics.go:40:49: too many arguments in call to prometheus.NewProcessCollector
|
// ../../go/pkg/mod/github.com/coredns/coredns@v1.2.0/plugin/metrics/metrics.go:40:49: too many arguments in call to prometheus.NewProcessCollector
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -9,9 +9,9 @@ github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFD
|
||||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
|
||||||
github.com/acmacalister/skittles v0.0.0-20160609003031-7423546701e1 h1:RKnVV4C7qoN/sToLX2y1dqH7T6kKLMHcwRJlgwb9Ggk=
|
github.com/acmacalister/skittles v0.0.0-20160609003031-7423546701e1 h1:RKnVV4C7qoN/sToLX2y1dqH7T6kKLMHcwRJlgwb9Ggk=
|
||||||
github.com/acmacalister/skittles v0.0.0-20160609003031-7423546701e1/go.mod h1:gI5CyA/CEnS6eqNV22rqs4dG3aGfaSbXgPORIlwr2r0=
|
github.com/acmacalister/skittles v0.0.0-20160609003031-7423546701e1/go.mod h1:gI5CyA/CEnS6eqNV22rqs4dG3aGfaSbXgPORIlwr2r0=
|
||||||
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
@ -285,3 +285,5 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
zombiezen.com/go/capnproto2 v0.0.0-20180616160808-7cfd211c19c7 h1:CZoOFlTPbKfAShKYrMuUfYbnXexFT1rYRUX1SPnrdE4=
|
zombiezen.com/go/capnproto2 v0.0.0-20180616160808-7cfd211c19c7 h1:CZoOFlTPbKfAShKYrMuUfYbnXexFT1rYRUX1SPnrdE4=
|
||||||
zombiezen.com/go/capnproto2 v0.0.0-20180616160808-7cfd211c19c7/go.mod h1:TMGa8HWGJkXiq4nHe9Zu/JgRF5oUtg4XizFC+Vexbec=
|
zombiezen.com/go/capnproto2 v0.0.0-20180616160808-7cfd211c19c7/go.mod h1:TMGa8HWGJkXiq4nHe9Zu/JgRF5oUtg4XizFC+Vexbec=
|
||||||
|
zombiezen.com/go/capnproto2 v2.18.0+incompatible h1:mwfXZniffG5mXokQGHUJWGnqIBggoPfT/CEwon9Yess=
|
||||||
|
zombiezen.com/go/capnproto2 v2.18.0+incompatible/go.mod h1:XO5Pr2SbXgqZwn0m0Ru54QBqpOf4K5AYBO+8LAOBQEQ=
|
||||||
|
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
"github.com/cloudflare/cloudflared/h2mux"
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
"github.com/cloudflare/cloudflared/logger"
|
||||||
"github.com/cloudflare/cloudflared/signal"
|
"github.com/cloudflare/cloudflared/signal"
|
||||||
"github.com/cloudflare/cloudflared/streamhandler"
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||||
"github.com/cloudflare/cloudflared/validation"
|
"github.com/cloudflare/cloudflared/validation"
|
||||||
|
@ -618,8 +617,8 @@ func (h *TunnelHandler) ServeStream(stream *h2mux.MuxedStream) error {
|
||||||
return reqErr
|
return reqErr
|
||||||
}
|
}
|
||||||
|
|
||||||
cfRay := streamhandler.FindCfRayHeader(req)
|
cfRay := findCfRayHeader(req)
|
||||||
lbProbe := streamhandler.IsLBProbeRequest(req)
|
lbProbe := isLBProbeRequest(req)
|
||||||
h.logRequest(req, cfRay, lbProbe)
|
h.logRequest(req, cfRay, lbProbe)
|
||||||
|
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
|
@ -833,3 +832,11 @@ func activeIncidentsMsg(incidents []Incident) string {
|
||||||
return preamble + " " + strings.Join(incidentStrings, "; ")
|
return preamble + " " + strings.Join(incidentStrings, "; ")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findCfRayHeader(h1 *http.Request) string {
|
||||||
|
return h1.Header.Get("Cf-Ray")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLBProbeRequest(req *http.Request) bool {
|
||||||
|
return strings.HasPrefix(req.UserAgent(), lbProbeUserAgentPrefix)
|
||||||
|
}
|
||||||
|
|
|
@ -1,247 +0,0 @@
|
||||||
// Package client defines and implements interface to proxy to HTTP, websocket and hello world origins
|
|
||||||
package originservice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/buffer"
|
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
|
||||||
"github.com/cloudflare/cloudflared/hello"
|
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
|
||||||
"github.com/cloudflare/cloudflared/websocket"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OriginService is an interface to proxy requests to different type of origins
|
|
||||||
type OriginService interface {
|
|
||||||
Proxy(stream *h2mux.MuxedStream, req *http.Request) (resp *http.Response, err error)
|
|
||||||
URL() *url.URL
|
|
||||||
Summary() string
|
|
||||||
Shutdown()
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPService talks to origin using HTTP/HTTPS
|
|
||||||
type HTTPService struct {
|
|
||||||
client http.RoundTripper
|
|
||||||
originURL *url.URL
|
|
||||||
chunkedEncoding bool
|
|
||||||
bufferPool *buffer.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHTTPService(transport http.RoundTripper, url *url.URL, chunkedEncoding bool) OriginService {
|
|
||||||
return &HTTPService{
|
|
||||||
client: transport,
|
|
||||||
originURL: url,
|
|
||||||
chunkedEncoding: chunkedEncoding,
|
|
||||||
bufferPool: buffer.NewPool(512 * 1024),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *HTTPService) Proxy(stream *h2mux.MuxedStream, req *http.Request) (*http.Response, error) {
|
|
||||||
const responseSourceOrigin = "origin"
|
|
||||||
|
|
||||||
// Support for WSGI Servers by switching transfer encoding from chunked to gzip/deflate
|
|
||||||
if !hc.chunkedEncoding {
|
|
||||||
req.TransferEncoding = []string{"gzip", "deflate"}
|
|
||||||
cLength, err := strconv.Atoi(req.Header.Get("Content-Length"))
|
|
||||||
if err == nil {
|
|
||||||
req.ContentLength = int64(cLength)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request origin to keep connection alive to improve performance
|
|
||||||
req.Header.Set("Connection", "keep-alive")
|
|
||||||
|
|
||||||
resp, err := hc.client.RoundTrip(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "error proxying request to HTTP origin")
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
responseHeaders := h1ResponseToH2Response(resp)
|
|
||||||
responseHeaders = append(responseHeaders, h2mux.CreateResponseMetaHeader(h2mux.ResponseMetaHeaderField, responseSourceOrigin))
|
|
||||||
err = stream.WriteHeaders(responseHeaders)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "error writing response header to HTTP origin")
|
|
||||||
}
|
|
||||||
if isEventStream(resp) {
|
|
||||||
writeEventStream(stream, resp.Body)
|
|
||||||
} else {
|
|
||||||
// Use CopyBuffer, because Copy only allocates a 32KiB buffer, and cross-stream
|
|
||||||
// compression generates dictionary on first write
|
|
||||||
buf := hc.bufferPool.Get()
|
|
||||||
defer hc.bufferPool.Put(buf)
|
|
||||||
io.CopyBuffer(stream, resp.Body, buf)
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *HTTPService) URL() *url.URL {
|
|
||||||
return hc.originURL
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *HTTPService) Summary() string {
|
|
||||||
return fmt.Sprintf("HTTP service listening on %s", hc.originURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *HTTPService) Shutdown() {}
|
|
||||||
|
|
||||||
// WebsocketService talks to origin using WS/WSS
|
|
||||||
type WebsocketService struct {
|
|
||||||
tlsConfig *tls.Config
|
|
||||||
originURL *url.URL
|
|
||||||
shutdownC chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWebSocketService(tlsConfig *tls.Config, url *url.URL, logger logger.Service) (OriginService, error) {
|
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:")
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot start Websocket Proxy Server")
|
|
||||||
}
|
|
||||||
shutdownC := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
websocket.StartProxyServer(logger, listener, url.String(), shutdownC, websocket.DefaultStreamHandler)
|
|
||||||
}()
|
|
||||||
return &WebsocketService{
|
|
||||||
tlsConfig: tlsConfig,
|
|
||||||
originURL: url,
|
|
||||||
shutdownC: shutdownC,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wsc *WebsocketService) Proxy(stream *h2mux.MuxedStream, req *http.Request) (*http.Response, error) {
|
|
||||||
if !websocket.IsWebSocketUpgrade(req) {
|
|
||||||
return nil, fmt.Errorf("request is not a websocket connection")
|
|
||||||
}
|
|
||||||
conn, response, err := websocket.ClientConnect(req, wsc.tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
err = stream.WriteHeaders(h1ResponseToH2Response(response))
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "error writing response header to websocket origin")
|
|
||||||
}
|
|
||||||
// Copy to/from stream to the undelying connection. Use the underlying
|
|
||||||
// connection because cloudflared doesn't operate on the message themselves
|
|
||||||
websocket.Stream(conn.UnderlyingConn(), stream)
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wsc *WebsocketService) URL() *url.URL {
|
|
||||||
return wsc.originURL
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wsc *WebsocketService) Summary() string {
|
|
||||||
return fmt.Sprintf("Websocket listening on %s", wsc.originURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wsc *WebsocketService) Shutdown() {
|
|
||||||
close(wsc.shutdownC)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HelloWorldService talks to the hello world example origin
|
|
||||||
type HelloWorldService struct {
|
|
||||||
client http.RoundTripper
|
|
||||||
listener net.Listener
|
|
||||||
originURL *url.URL
|
|
||||||
shutdownC chan struct{}
|
|
||||||
bufferPool *buffer.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHelloWorldService(transport http.RoundTripper, logger logger.Service) (OriginService, error) {
|
|
||||||
listener, err := hello.CreateTLSListener("127.0.0.1:")
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot start Hello World Server")
|
|
||||||
}
|
|
||||||
shutdownC := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
hello.StartHelloWorldServer(logger, listener, shutdownC)
|
|
||||||
}()
|
|
||||||
return &HelloWorldService{
|
|
||||||
client: transport,
|
|
||||||
listener: listener,
|
|
||||||
originURL: &url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
Host: listener.Addr().String(),
|
|
||||||
},
|
|
||||||
shutdownC: shutdownC,
|
|
||||||
bufferPool: buffer.NewPool(512 * 1024),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hwc *HelloWorldService) Proxy(stream *h2mux.MuxedStream, req *http.Request) (*http.Response, error) {
|
|
||||||
// Request origin to keep connection alive to improve performance
|
|
||||||
req.Header.Set("Connection", "keep-alive")
|
|
||||||
resp, err := hwc.client.RoundTrip(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "error proxying request to Hello World origin")
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
err = stream.WriteHeaders(h1ResponseToH2Response(resp))
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "error writing response header to Hello World origin")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use CopyBuffer, because Copy only allocates a 32KiB buffer, and cross-stream
|
|
||||||
// compression generates dictionary on first write
|
|
||||||
buf := hwc.bufferPool.Get()
|
|
||||||
defer hwc.bufferPool.Put(buf)
|
|
||||||
io.CopyBuffer(stream, resp.Body, buf)
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hwc *HelloWorldService) URL() *url.URL {
|
|
||||||
return hwc.originURL
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hwc *HelloWorldService) Summary() string {
|
|
||||||
return fmt.Sprintf("Hello World service listening on %s", hwc.originURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hwc *HelloWorldService) Shutdown() {
|
|
||||||
hwc.listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func isEventStream(resp *http.Response) bool {
|
|
||||||
// Check if content-type is text/event-stream. We need to check if the header value starts with text/event-stream
|
|
||||||
// because text/event-stream; charset=UTF-8 is also valid
|
|
||||||
// Ref: https://tools.ietf.org/html/rfc7231#section-3.1.1.1
|
|
||||||
for _, contentType := range resp.Header["content-type"] {
|
|
||||||
if strings.HasPrefix(strings.ToLower(contentType), "text/event-stream") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeEventStream(stream *h2mux.MuxedStream, respBody io.ReadCloser) {
|
|
||||||
reader := bufio.NewReader(respBody)
|
|
||||||
for {
|
|
||||||
line, err := reader.ReadBytes('\n')
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
stream.Write(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func h1ResponseToH2Response(h1 *http.Response) (h2 []h2mux.Header) {
|
|
||||||
h2 = []h2mux.Header{{Name: ":status", Value: fmt.Sprintf("%d", h1.StatusCode)}}
|
|
||||||
for headerName, headerValues := range h1.Header {
|
|
||||||
for _, headerValue := range headerValues {
|
|
||||||
h2 = append(h2, h2mux.Header{Name: strings.ToLower(headerName), Value: headerValue})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
package originservice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIsEventStream(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
resp *http.Response
|
|
||||||
isEventStream bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
resp: &http.Response{},
|
|
||||||
isEventStream: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// isEventStream checks all headers
|
|
||||||
resp: &http.Response{
|
|
||||||
Header: http.Header{
|
|
||||||
"accept": []string{"text/html"},
|
|
||||||
"content-type": []string{"text/event-stream"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
isEventStream: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Content-Type and text/event-stream are case-insensitive. text/event-stream can be followed by OWS parameter
|
|
||||||
resp: &http.Response{
|
|
||||||
Header: http.Header{
|
|
||||||
"content-type": []string{"Text/event-stream;charset=utf-8"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
isEventStream: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Content-Type and text/event-stream are case-insensitive. text/event-stream can be followed by OWS parameter
|
|
||||||
resp: &http.Response{
|
|
||||||
Header: http.Header{
|
|
||||||
"content-type": []string{"appication/json", "text/html", "Text/event-stream;charset=utf-8"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
isEventStream: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Not an event stream because the content-type value doesn't start with text/event-stream
|
|
||||||
resp: &http.Response{
|
|
||||||
Header: http.Header{
|
|
||||||
"content-type": []string{" text/event-stream"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
isEventStream: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
assert.Equal(t, test.isEventStream, isEventStream(test.resp), "Header: %v", test.resp.Header)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
package streamhandler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
lbProbeUserAgentPrefix = "Mozilla/5.0 (compatible; Cloudflare-Traffic-Manager/1.0; +https://www.cloudflare.com/traffic-manager/;"
|
|
||||||
)
|
|
||||||
|
|
||||||
func FindCfRayHeader(h1 *http.Request) string {
|
|
||||||
return h1.Header.Get("Cf-Ray")
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsLBProbeRequest(req *http.Request) bool {
|
|
||||||
return strings.HasPrefix(req.UserAgent(), lbProbeUserAgentPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createRequest(stream *h2mux.MuxedStream, url *url.URL) (*http.Request, error) {
|
|
||||||
req, err := http.NewRequest(http.MethodGet, url.String(), h2mux.MuxedStreamReader{MuxedStream: stream})
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "unexpected error from http.NewRequest")
|
|
||||||
}
|
|
||||||
err = h2mux.H2RequestHeadersToH1Request(stream.Headers, req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "invalid request received")
|
|
||||||
}
|
|
||||||
return req, nil
|
|
||||||
}
|
|
|
@ -1,189 +0,0 @@
|
||||||
package streamhandler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelhostnamemapper"
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"zombiezen.com/go/capnproto2/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
statusPseudoHeader = ":status"
|
|
||||||
)
|
|
||||||
|
|
||||||
type httpErrorStatus struct {
|
|
||||||
status string
|
|
||||||
text []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
statusBadRequest = newHTTPErrorStatus(http.StatusBadRequest)
|
|
||||||
statusNotFound = newHTTPErrorStatus(http.StatusNotFound)
|
|
||||||
statusBadGateway = newHTTPErrorStatus(http.StatusBadGateway)
|
|
||||||
)
|
|
||||||
|
|
||||||
func newHTTPErrorStatus(status int) *httpErrorStatus {
|
|
||||||
return &httpErrorStatus{
|
|
||||||
status: strconv.Itoa(status),
|
|
||||||
text: []byte(http.StatusText(status)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamHandler handles new stream opened by the edge. The streams can be used to proxy requests or make RPC.
|
|
||||||
type StreamHandler struct {
|
|
||||||
// newConfigChan is a send-only channel to notify Supervisor of a new ClientConfig
|
|
||||||
newConfigChan chan<- *pogs.ClientConfig
|
|
||||||
// useConfigResultChan is a receive-only channel for Supervisor to communicate the result of applying a new ClientConfig
|
|
||||||
useConfigResultChan <-chan *pogs.UseConfigurationResult
|
|
||||||
// originMapper maps tunnel hostname to origin service
|
|
||||||
tunnelHostnameMapper *tunnelhostnamemapper.TunnelHostnameMapper
|
|
||||||
logger logger.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStreamHandler creates a new StreamHandler
|
|
||||||
func NewStreamHandler(newConfigChan chan<- *pogs.ClientConfig,
|
|
||||||
useConfigResultChan <-chan *pogs.UseConfigurationResult,
|
|
||||||
logger logger.Service,
|
|
||||||
) *StreamHandler {
|
|
||||||
return &StreamHandler{
|
|
||||||
newConfigChan: newConfigChan,
|
|
||||||
useConfigResultChan: useConfigResultChan,
|
|
||||||
tunnelHostnameMapper: tunnelhostnamemapper.NewTunnelHostnameMapper(),
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UseConfiguration implements ClientService
|
|
||||||
func (s *StreamHandler) UseConfiguration(ctx context.Context, config *pogs.ClientConfig) (*pogs.UseConfigurationResult, error) {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
err := fmt.Errorf("Timeout while sending new config to Supervisor")
|
|
||||||
s.logger.Errorf("streamHandler: %s", err)
|
|
||||||
return nil, err
|
|
||||||
case s.newConfigChan <- config:
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
err := fmt.Errorf("Timeout applying new configuration")
|
|
||||||
s.logger.Errorf("streamHandler: %s", err)
|
|
||||||
return nil, err
|
|
||||||
case result := <-s.useConfigResultChan:
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateConfig replaces current originmapper mapping with mappings from newConfig
|
|
||||||
func (s *StreamHandler) UpdateConfig(newConfig []*pogs.ReverseProxyConfig) (failedConfigs []*pogs.FailedConfig) {
|
|
||||||
|
|
||||||
// Delete old configs that aren't in the `newConfig`
|
|
||||||
toRemove := s.tunnelHostnameMapper.ToRemove(newConfig)
|
|
||||||
for _, hostnameToRemove := range toRemove {
|
|
||||||
s.tunnelHostnameMapper.Delete(hostnameToRemove)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new configs that weren't in the old mapper
|
|
||||||
toAdd := s.tunnelHostnameMapper.ToAdd(newConfig)
|
|
||||||
for _, tunnelConfig := range toAdd {
|
|
||||||
tunnelHostname := tunnelConfig.TunnelHostname
|
|
||||||
originSerice, err := tunnelConfig.OriginConfig.Service(s.logger)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Errorf("streamHandler: tunnelHostname: %s Invalid origin service config: %s", tunnelHostname, err)
|
|
||||||
failedConfigs = append(failedConfigs, &pogs.FailedConfig{
|
|
||||||
Config: tunnelConfig,
|
|
||||||
Reason: tunnelConfig.FailReason(err),
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s.tunnelHostnameMapper.Add(tunnelConfig.TunnelHostname, originSerice)
|
|
||||||
s.logger.Infof("streamHandler: tunnelHostname: %s New origin service config: %v", tunnelHostname, originSerice.Summary())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeStream implements MuxedStreamHandler interface
|
|
||||||
func (s *StreamHandler) ServeStream(stream *h2mux.MuxedStream) error {
|
|
||||||
if stream.IsRPCStream() {
|
|
||||||
return s.serveRPC(stream)
|
|
||||||
}
|
|
||||||
if err := s.serveRequest(stream); err != nil {
|
|
||||||
s.logger.Errorf("streamHandler: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StreamHandler) serveRPC(stream *h2mux.MuxedStream) error {
|
|
||||||
stream.WriteHeaders([]h2mux.Header{{Name: ":status", Value: "200"}})
|
|
||||||
main := pogs.ClientService_ServerToClient(s)
|
|
||||||
rpcConn := rpc.NewConn(
|
|
||||||
tunnelrpc.NewTransportLogger(s.logger, rpc.StreamTransport(stream)),
|
|
||||||
rpc.MainInterface(main.Client),
|
|
||||||
tunnelrpc.ConnLog(s.logger),
|
|
||||||
)
|
|
||||||
return rpcConn.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StreamHandler) serveRequest(stream *h2mux.MuxedStream) error {
|
|
||||||
tunnelHostname := stream.TunnelHostname()
|
|
||||||
if !tunnelHostname.IsSet() {
|
|
||||||
s.writeErrorStatus(stream, statusBadRequest)
|
|
||||||
return fmt.Errorf("stream doesn't have tunnelHostname")
|
|
||||||
}
|
|
||||||
|
|
||||||
originService, ok := s.tunnelHostnameMapper.Get(tunnelHostname)
|
|
||||||
if !ok {
|
|
||||||
s.writeErrorStatus(stream, statusNotFound)
|
|
||||||
return fmt.Errorf("cannot map tunnel hostname %s to origin", tunnelHostname)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := createRequest(stream, originService.URL())
|
|
||||||
if err != nil {
|
|
||||||
s.writeErrorStatus(stream, statusBadRequest)
|
|
||||||
return errors.Wrap(err, "cannot create request")
|
|
||||||
}
|
|
||||||
|
|
||||||
cfRay := s.logRequest(req, tunnelHostname)
|
|
||||||
s.logger.Debugf("streamHandler: tunnelHostname: %s CF-RAY: %s Request Headers %+v", tunnelHostname, cfRay, req.Header)
|
|
||||||
|
|
||||||
resp, err := originService.Proxy(stream, req)
|
|
||||||
if err != nil {
|
|
||||||
s.writeErrorStatus(stream, statusBadGateway)
|
|
||||||
return errors.Wrap(err, "cannot proxy request")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.logger.Debugf("streamHandler: tunnelHostname: %s CF-RAY: %s status: %s Response Headers %+v", tunnelHostname, cfRay, resp.Status, resp.Header)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StreamHandler) logRequest(req *http.Request, tunnelHostname h2mux.TunnelHostname) string {
|
|
||||||
cfRay := FindCfRayHeader(req)
|
|
||||||
lbProbe := IsLBProbeRequest(req)
|
|
||||||
logger := s.logger
|
|
||||||
if cfRay != "" {
|
|
||||||
logger.Debugf("streamHandler: tunnelHostname: %s CF-RAY: %s %s %s %s", tunnelHostname, cfRay, req.Method, req.URL, req.Proto)
|
|
||||||
} else if lbProbe {
|
|
||||||
logger.Debugf("streamHandler: tunnelHostname: %s CF-RAY: %s Load Balancer health check %s %s %s", tunnelHostname, cfRay, req.Method, req.URL, req.Proto)
|
|
||||||
} else {
|
|
||||||
logger.Infof("streamHandler: tunnelHostname: %s CF-RAY: %s Requests %v does not have CF-RAY header. Please open a support ticket with Cloudflare.", tunnelHostname, cfRay, req)
|
|
||||||
}
|
|
||||||
return cfRay
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StreamHandler) writeErrorStatus(stream *h2mux.MuxedStream, status *httpErrorStatus) {
|
|
||||||
_ = stream.WriteHeaders([]h2mux.Header{
|
|
||||||
{
|
|
||||||
Name: statusPseudoHeader,
|
|
||||||
Value: status.status,
|
|
||||||
},
|
|
||||||
h2mux.CreateResponseMetaHeader(h2mux.ResponseMetaHeaderField, h2mux.ResponseSourceCloudflared),
|
|
||||||
})
|
|
||||||
_, _ = stream.Write(status.text)
|
|
||||||
}
|
|
|
@ -1,261 +0,0 @@
|
||||||
package streamhandler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
testOpenStreamTimeout = time.Millisecond * 5000
|
|
||||||
testHandshakeTimeout = time.Millisecond * 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
testTunnelHostname = h2mux.TunnelHostname("123.cftunnel.com")
|
|
||||||
baseHeaders = []h2mux.Header{
|
|
||||||
{Name: ":method", Value: "GET"},
|
|
||||||
{Name: ":scheme", Value: "http"},
|
|
||||||
{Name: ":authority", Value: "example.com"},
|
|
||||||
{Name: ":path", Value: "/"},
|
|
||||||
|
|
||||||
// Regular headers must always come after the pseudoheaders
|
|
||||||
{Name: h2mux.RequestUserHeadersField, Value: ""},
|
|
||||||
}
|
|
||||||
tunnelHostnameHeader = h2mux.Header{Name: h2mux.CloudflaredProxyTunnelHostnameHeader, Value: testTunnelHostname.String()}
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestServeRequest(t *testing.T) {
|
|
||||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
|
||||||
configChan := make(chan *pogs.ClientConfig)
|
|
||||||
useConfigResultChan := make(chan *pogs.UseConfigurationResult)
|
|
||||||
streamHandler := NewStreamHandler(configChan, useConfigResultChan, l)
|
|
||||||
|
|
||||||
message := []byte("Hello cloudflared")
|
|
||||||
httpServer := httptest.NewServer(&mockHTTPHandler{message})
|
|
||||||
|
|
||||||
reverseProxyConfigs := []*pogs.ReverseProxyConfig{
|
|
||||||
{
|
|
||||||
TunnelHostname: testTunnelHostname,
|
|
||||||
OriginConfig: &pogs.HTTPOriginConfig{
|
|
||||||
URLString: httpServer.URL,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
streamHandler.UpdateConfig(reverseProxyConfigs)
|
|
||||||
|
|
||||||
muxPair := NewDefaultMuxerPair(t, streamHandler)
|
|
||||||
muxPair.Serve(t)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testOpenStreamTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
headers := append(baseHeaders, tunnelHostnameHeader)
|
|
||||||
stream, err := muxPair.EdgeMux.OpenStream(ctx, headers, nil)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assertStatusHeader(t, http.StatusOK, stream.Headers)
|
|
||||||
assertRespBody(t, message, stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createStreamHandler() *StreamHandler {
|
|
||||||
configChan := make(chan *pogs.ClientConfig)
|
|
||||||
useConfigResultChan := make(chan *pogs.UseConfigurationResult)
|
|
||||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
|
||||||
|
|
||||||
return NewStreamHandler(configChan, useConfigResultChan, l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createRequestMuxPair(t *testing.T, streamHandler *StreamHandler) *DefaultMuxerPair {
|
|
||||||
muxPair := NewDefaultMuxerPair(t, streamHandler)
|
|
||||||
muxPair.Serve(t)
|
|
||||||
|
|
||||||
return muxPair
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServeStatusBadRequest(t *testing.T) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testOpenStreamTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// No tunnel hostname header, expect to get 400 Bad Request
|
|
||||||
stream, err := createRequestMuxPair(t, createStreamHandler()).EdgeMux.OpenStream(ctx, baseHeaders, nil)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assertStatusHeader(t, http.StatusBadRequest, stream.Headers)
|
|
||||||
assertRespBody(t, statusBadRequest.text, stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServeInvalidContentLength(t *testing.T) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testOpenStreamTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Invalid content-length, wouldn't be able to create a request
|
|
||||||
// Expect to get 400 Bad Request
|
|
||||||
headers := append(baseHeaders, tunnelHostnameHeader)
|
|
||||||
headers = append(headers, h2mux.Header{
|
|
||||||
Name: "content-length",
|
|
||||||
Value: "x",
|
|
||||||
})
|
|
||||||
streamHandler := createStreamHandler()
|
|
||||||
streamHandler.UpdateConfig([]*pogs.ReverseProxyConfig{
|
|
||||||
{
|
|
||||||
TunnelHostname: testTunnelHostname,
|
|
||||||
OriginConfig: &pogs.HTTPOriginConfig{
|
|
||||||
URLString: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
mux := createRequestMuxPair(t, streamHandler).EdgeMux
|
|
||||||
stream, err := mux.OpenStream(ctx, headers, nil)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assertStatusHeader(t, http.StatusBadRequest, stream.Headers)
|
|
||||||
assertRespBody(t, statusBadRequest.text, stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServeStatusNotFound(t *testing.T) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testOpenStreamTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// No mapping for the tunnel hostname, expect to get 404 Not Found
|
|
||||||
headers := append(baseHeaders, tunnelHostnameHeader)
|
|
||||||
stream, err := createRequestMuxPair(t, createStreamHandler()).EdgeMux.OpenStream(ctx, headers, nil)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assertStatusHeader(t, http.StatusNotFound, stream.Headers)
|
|
||||||
assertRespBody(t, statusNotFound.text, stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServeStatusBadGateway(t *testing.T) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testOpenStreamTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Nothing listening on empty url, so proxy would fail. Expect to get 502 Bad Gateway
|
|
||||||
reverseProxyConfigs := []*pogs.ReverseProxyConfig{
|
|
||||||
{
|
|
||||||
TunnelHostname: testTunnelHostname,
|
|
||||||
OriginConfig: &pogs.HTTPOriginConfig{
|
|
||||||
URLString: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
streamHandler := createStreamHandler()
|
|
||||||
streamHandler.UpdateConfig(reverseProxyConfigs)
|
|
||||||
headers := append(baseHeaders, tunnelHostnameHeader)
|
|
||||||
stream, err := createRequestMuxPair(t, streamHandler).EdgeMux.OpenStream(ctx, headers, nil)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assertStatusHeader(t, http.StatusBadGateway, stream.Headers)
|
|
||||||
assertRespBody(t, statusBadGateway.text, stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertStatusHeader(t *testing.T, expectedStatus int, headers []h2mux.Header) {
|
|
||||||
assert.Equal(t, statusPseudoHeader, headers[0].Name)
|
|
||||||
assert.Equal(t, strconv.Itoa(expectedStatus), headers[0].Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertRespBody(t *testing.T, expectedRespBody []byte, stream *h2mux.MuxedStream) {
|
|
||||||
respBody := make([]byte, len(expectedRespBody))
|
|
||||||
_, err := stream.Read(respBody)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, expectedRespBody, respBody)
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultMuxerPair struct {
|
|
||||||
OriginMuxConfig h2mux.MuxerConfig
|
|
||||||
OriginMux *h2mux.Muxer
|
|
||||||
OriginConn net.Conn
|
|
||||||
EdgeMuxConfig h2mux.MuxerConfig
|
|
||||||
EdgeMux *h2mux.Muxer
|
|
||||||
EdgeConn net.Conn
|
|
||||||
doneC chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDefaultMuxerPair(t *testing.T, h h2mux.MuxedStreamHandler) *DefaultMuxerPair {
|
|
||||||
origin, edge := net.Pipe()
|
|
||||||
p := &DefaultMuxerPair{
|
|
||||||
OriginMuxConfig: h2mux.MuxerConfig{
|
|
||||||
Timeout: testHandshakeTimeout,
|
|
||||||
Handler: h,
|
|
||||||
IsClient: true,
|
|
||||||
Name: "origin",
|
|
||||||
Logger: logger.NewOutputWriter(logger.NewMockWriteManager()),
|
|
||||||
DefaultWindowSize: (1 << 8) - 1,
|
|
||||||
MaxWindowSize: (1 << 15) - 1,
|
|
||||||
StreamWriteBufferMaxLen: 1024,
|
|
||||||
},
|
|
||||||
OriginConn: origin,
|
|
||||||
EdgeMuxConfig: h2mux.MuxerConfig{
|
|
||||||
Timeout: testHandshakeTimeout,
|
|
||||||
IsClient: false,
|
|
||||||
Name: "edge",
|
|
||||||
Logger: logger.NewOutputWriter(logger.NewMockWriteManager()),
|
|
||||||
DefaultWindowSize: (1 << 8) - 1,
|
|
||||||
MaxWindowSize: (1 << 15) - 1,
|
|
||||||
StreamWriteBufferMaxLen: 1024,
|
|
||||||
},
|
|
||||||
EdgeConn: edge,
|
|
||||||
doneC: make(chan struct{}),
|
|
||||||
}
|
|
||||||
assert.NoError(t, p.Handshake(t.Name()))
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DefaultMuxerPair) Handshake(testName string) error {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testHandshakeTimeout)
|
|
||||||
defer cancel()
|
|
||||||
errGroup, _ := errgroup.WithContext(ctx)
|
|
||||||
errGroup.Go(func() (err error) {
|
|
||||||
p.EdgeMux, err = h2mux.Handshake(p.EdgeConn, p.EdgeConn, p.EdgeMuxConfig, h2mux.NewActiveStreamsMetrics(testName, "edge"))
|
|
||||||
return errors.Wrap(err, "edge handshake failure")
|
|
||||||
})
|
|
||||||
errGroup.Go(func() (err error) {
|
|
||||||
p.OriginMux, err = h2mux.Handshake(p.OriginConn, p.OriginConn, p.OriginMuxConfig, h2mux.NewActiveStreamsMetrics(testName, "origin"))
|
|
||||||
return errors.Wrap(err, "origin handshake failure")
|
|
||||||
})
|
|
||||||
|
|
||||||
return errGroup.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DefaultMuxerPair) Serve(t assert.TestingT) {
|
|
||||||
ctx := context.Background()
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(2)
|
|
||||||
go func() {
|
|
||||||
err := p.EdgeMux.Serve(ctx)
|
|
||||||
if err != nil && err != io.EOF && err != io.ErrClosedPipe {
|
|
||||||
t.Errorf("error in edge muxer Serve(): %s", err)
|
|
||||||
}
|
|
||||||
p.OriginMux.Shutdown()
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
err := p.OriginMux.Serve(ctx)
|
|
||||||
if err != nil && err != io.EOF && err != io.ErrClosedPipe {
|
|
||||||
t.Errorf("error in origin muxer Serve(): %s", err)
|
|
||||||
}
|
|
||||||
p.EdgeMux.Shutdown()
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
// notify when both muxes have stopped serving
|
|
||||||
wg.Wait()
|
|
||||||
close(p.doneC)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockHTTPHandler struct {
|
|
||||||
message []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mth *mockHTTPHandler) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
_, _ = w.Write(mth.message)
|
|
||||||
}
|
|
|
@ -1,205 +0,0 @@
|
||||||
// Package supervisor is used by declarative tunnels to get/apply new config from the edge.
|
|
||||||
package supervisor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
|
|
||||||
"github.com/cloudflare/cloudflared/connection"
|
|
||||||
"github.com/cloudflare/cloudflared/edgediscovery"
|
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
|
||||||
"github.com/cloudflare/cloudflared/streamhandler"
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Supervisor struct {
|
|
||||||
connManager *connection.EdgeManager
|
|
||||||
streamHandler *streamhandler.StreamHandler
|
|
||||||
autoupdater *updater.AutoUpdater
|
|
||||||
supportAutoupdate bool
|
|
||||||
newConfigChan <-chan *pogs.ClientConfig
|
|
||||||
useConfigResultChan chan<- *pogs.UseConfigurationResult
|
|
||||||
state *state
|
|
||||||
logger logger.Service
|
|
||||||
metrics metrics
|
|
||||||
}
|
|
||||||
|
|
||||||
type metrics struct {
|
|
||||||
configVersion prometheus.Gauge
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMetrics() metrics {
|
|
||||||
configVersion := prometheus.NewGauge(prometheus.GaugeOpts{
|
|
||||||
Namespace: "supervisor",
|
|
||||||
Subsystem: "supervisor",
|
|
||||||
Name: "config_version",
|
|
||||||
Help: "Latest configuration version received from Cloudflare",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
prometheus.MustRegister(
|
|
||||||
configVersion,
|
|
||||||
)
|
|
||||||
return metrics{
|
|
||||||
configVersion: configVersion,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSupervisor(
|
|
||||||
defaultClientConfig *pogs.ClientConfig,
|
|
||||||
userCredential []byte,
|
|
||||||
tlsConfig *tls.Config,
|
|
||||||
serviceDiscoverer *edgediscovery.Edge,
|
|
||||||
cloudflaredConfig *connection.CloudflaredConfig,
|
|
||||||
autoupdater *updater.AutoUpdater,
|
|
||||||
supportAutoupdate bool,
|
|
||||||
logger logger.Service,
|
|
||||||
) (*Supervisor, error) {
|
|
||||||
newConfigChan := make(chan *pogs.ClientConfig)
|
|
||||||
useConfigResultChan := make(chan *pogs.UseConfigurationResult)
|
|
||||||
streamHandler := streamhandler.NewStreamHandler(newConfigChan, useConfigResultChan, logger)
|
|
||||||
invalidConfigs := streamHandler.UpdateConfig(defaultClientConfig.ReverseProxyConfigs)
|
|
||||||
|
|
||||||
if len(invalidConfigs) > 0 {
|
|
||||||
for _, invalidConfig := range invalidConfigs {
|
|
||||||
logger.Errorf("Tunnel %+v is invalid, reason: %s", invalidConfig.Config, invalidConfig.Reason)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("At least 1 Tunnel config is invalid")
|
|
||||||
}
|
|
||||||
|
|
||||||
tunnelHostnames := make([]h2mux.TunnelHostname, len(defaultClientConfig.ReverseProxyConfigs))
|
|
||||||
for i, reverseProxyConfig := range defaultClientConfig.ReverseProxyConfigs {
|
|
||||||
tunnelHostnames[i] = reverseProxyConfig.TunnelHostname
|
|
||||||
}
|
|
||||||
defaultEdgeMgrConfigurable := &connection.EdgeManagerConfigurable{
|
|
||||||
TunnelHostnames: tunnelHostnames,
|
|
||||||
EdgeConnectionConfig: defaultClientConfig.EdgeConnectionConfig,
|
|
||||||
}
|
|
||||||
return &Supervisor{
|
|
||||||
connManager: connection.NewEdgeManager(streamHandler, defaultEdgeMgrConfigurable, userCredential, tlsConfig,
|
|
||||||
serviceDiscoverer, cloudflaredConfig, logger),
|
|
||||||
streamHandler: streamHandler,
|
|
||||||
autoupdater: autoupdater,
|
|
||||||
supportAutoupdate: supportAutoupdate,
|
|
||||||
newConfigChan: newConfigChan,
|
|
||||||
useConfigResultChan: useConfigResultChan,
|
|
||||||
state: newState(defaultClientConfig),
|
|
||||||
logger: logger,
|
|
||||||
metrics: newMetrics(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Supervisor) Run(ctx context.Context) error {
|
|
||||||
errGroup, groupCtx := errgroup.WithContext(ctx)
|
|
||||||
|
|
||||||
errGroup.Go(func() error {
|
|
||||||
return s.connManager.Run(groupCtx)
|
|
||||||
})
|
|
||||||
|
|
||||||
errGroup.Go(func() error {
|
|
||||||
return s.listenToNewConfig(groupCtx)
|
|
||||||
})
|
|
||||||
|
|
||||||
errGroup.Go(func() error {
|
|
||||||
return s.listenToShutdownSignal(groupCtx)
|
|
||||||
})
|
|
||||||
|
|
||||||
if s.supportAutoupdate {
|
|
||||||
errGroup.Go(func() error {
|
|
||||||
return s.autoupdater.Run(groupCtx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
err := errGroup.Wait()
|
|
||||||
s.logger.Errorf("Supervisor terminated, reason: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Supervisor) listenToShutdownSignal(serveCtx context.Context) error {
|
|
||||||
signals := make(chan os.Signal, 10)
|
|
||||||
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
|
|
||||||
defer signal.Stop(signals)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-serveCtx.Done():
|
|
||||||
return serveCtx.Err()
|
|
||||||
case sig := <-signals:
|
|
||||||
return fmt.Errorf("received %v signal", sig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Supervisor) listenToNewConfig(ctx context.Context) error {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case newConfig := <-s.newConfigChan:
|
|
||||||
s.useConfigResultChan <- s.notifySubsystemsNewConfig(newConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Supervisor) notifySubsystemsNewConfig(newConfig *pogs.ClientConfig) *pogs.UseConfigurationResult {
|
|
||||||
s.logger.Infof("Received configuration %v", newConfig.Version)
|
|
||||||
if s.state.hasAppliedVersion(newConfig.Version) {
|
|
||||||
s.logger.Infof("%v has been applied", newConfig.Version)
|
|
||||||
return &pogs.UseConfigurationResult{
|
|
||||||
Success: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.metrics.configVersion.Set(float64(newConfig.Version))
|
|
||||||
|
|
||||||
s.state.updateConfig(newConfig)
|
|
||||||
var tunnelHostnames []h2mux.TunnelHostname
|
|
||||||
for _, tunnelConfig := range newConfig.ReverseProxyConfigs {
|
|
||||||
tunnelHostnames = append(tunnelHostnames, tunnelConfig.TunnelHostname)
|
|
||||||
}
|
|
||||||
// Update connManager configurable
|
|
||||||
s.connManager.UpdateConfigurable(&connection.EdgeManagerConfigurable{
|
|
||||||
TunnelHostnames: tunnelHostnames,
|
|
||||||
EdgeConnectionConfig: newConfig.EdgeConnectionConfig,
|
|
||||||
})
|
|
||||||
// Update streamHandler tunnelHostnameMapper mapping
|
|
||||||
failedConfigs := s.streamHandler.UpdateConfig(newConfig.ReverseProxyConfigs)
|
|
||||||
|
|
||||||
if s.supportAutoupdate {
|
|
||||||
s.autoupdater.Update(newConfig.SupervisorConfig.AutoUpdateFrequency)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &pogs.UseConfigurationResult{
|
|
||||||
Success: len(failedConfigs) == 0,
|
|
||||||
FailedConfigs: failedConfigs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type state struct {
|
|
||||||
sync.RWMutex
|
|
||||||
currentConfig *pogs.ClientConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func newState(currentConfig *pogs.ClientConfig) *state {
|
|
||||||
return &state{
|
|
||||||
currentConfig: currentConfig,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) hasAppliedVersion(incomingVersion pogs.Version) bool {
|
|
||||||
s.RLock()
|
|
||||||
defer s.RUnlock()
|
|
||||||
return s.currentConfig.Version.IsNewerOrEqual(incomingVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) updateConfig(newConfig *pogs.ClientConfig) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
s.currentConfig = newConfig
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
package tunnelhostnamemapper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
|
||||||
"github.com/cloudflare/cloudflared/originservice"
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TunnelHostnameMapper maps TunnelHostname to an OriginService
|
|
||||||
type TunnelHostnameMapper struct {
|
|
||||||
sync.RWMutex
|
|
||||||
tunnelHostnameToOrigin map[h2mux.TunnelHostname]originservice.OriginService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTunnelHostnameMapper() *TunnelHostnameMapper {
|
|
||||||
return &TunnelHostnameMapper{
|
|
||||||
tunnelHostnameToOrigin: make(map[h2mux.TunnelHostname]originservice.OriginService),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get an OriginService given a TunnelHostname
|
|
||||||
func (om *TunnelHostnameMapper) Get(key h2mux.TunnelHostname) (originservice.OriginService, bool) {
|
|
||||||
om.RLock()
|
|
||||||
defer om.RUnlock()
|
|
||||||
originService, ok := om.tunnelHostnameToOrigin[key]
|
|
||||||
return originService, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a mapping. If there is already an OriginService with this key, shutdown the old origin service and replace it
|
|
||||||
// with the new one
|
|
||||||
func (om *TunnelHostnameMapper) Add(key h2mux.TunnelHostname, os originservice.OriginService) {
|
|
||||||
om.Lock()
|
|
||||||
defer om.Unlock()
|
|
||||||
if oldOS, ok := om.tunnelHostnameToOrigin[key]; ok {
|
|
||||||
oldOS.Shutdown()
|
|
||||||
}
|
|
||||||
om.tunnelHostnameToOrigin[key] = os
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete a mapping, and shutdown its OriginService
|
|
||||||
func (om *TunnelHostnameMapper) Delete(key h2mux.TunnelHostname) (keyFound bool) {
|
|
||||||
om.Lock()
|
|
||||||
defer om.Unlock()
|
|
||||||
if os, ok := om.tunnelHostnameToOrigin[key]; ok {
|
|
||||||
os.Shutdown()
|
|
||||||
delete(om.tunnelHostnameToOrigin, key)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToRemove finds all keys that should be removed from the TunnelHostnameMapper.
|
|
||||||
func (om *TunnelHostnameMapper) ToRemove(newConfigs []*pogs.ReverseProxyConfig) (toRemove []h2mux.TunnelHostname) {
|
|
||||||
om.Lock()
|
|
||||||
defer om.Unlock()
|
|
||||||
|
|
||||||
// Convert into a set, for O(1) lookups instead of O(n)
|
|
||||||
newConfigSet := toSet(newConfigs)
|
|
||||||
|
|
||||||
// If a config in `om` isn't in `newConfigs`, it must be removed.
|
|
||||||
for hostname := range om.tunnelHostnameToOrigin {
|
|
||||||
if _, ok := newConfigSet[hostname]; !ok {
|
|
||||||
toRemove = append(toRemove, hostname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToAdd filters the given configs, keeping those that should be added to the TunnelHostnameMapper.
|
|
||||||
func (om *TunnelHostnameMapper) ToAdd(newConfigs []*pogs.ReverseProxyConfig) (toAdd []*pogs.ReverseProxyConfig) {
|
|
||||||
om.Lock()
|
|
||||||
defer om.Unlock()
|
|
||||||
|
|
||||||
// If a config in `newConfigs` isn't in `om`, it must be added.
|
|
||||||
for _, config := range newConfigs {
|
|
||||||
if _, ok := om.tunnelHostnameToOrigin[config.TunnelHostname]; !ok {
|
|
||||||
toAdd = append(toAdd, config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func toSet(configs []*pogs.ReverseProxyConfig) map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig {
|
|
||||||
m := make(map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig)
|
|
||||||
for _, config := range configs {
|
|
||||||
m[config.TunnelHostname] = config
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
|
@ -1,212 +0,0 @@
|
||||||
package tunnelhostnamemapper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
|
||||||
"github.com/cloudflare/cloudflared/originservice"
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
routines = 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTunnelHostnameMapperConcurrentAccess(t *testing.T) {
|
|
||||||
thm := NewTunnelHostnameMapper()
|
|
||||||
|
|
||||||
concurrentOps(t, func(i int) {
|
|
||||||
// om is empty
|
|
||||||
os, ok := thm.Get(tunnelHostname(i))
|
|
||||||
assert.False(t, ok)
|
|
||||||
assert.Nil(t, os)
|
|
||||||
})
|
|
||||||
|
|
||||||
firstURL, err := url.Parse("https://127.0.0.1:8080")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
httpOS := originservice.NewHTTPService(http.DefaultTransport, firstURL, false)
|
|
||||||
concurrentOps(t, func(i int) {
|
|
||||||
thm.Add(tunnelHostname(i), httpOS)
|
|
||||||
})
|
|
||||||
|
|
||||||
concurrentOps(t, func(i int) {
|
|
||||||
os, ok := thm.Get(tunnelHostname(i))
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Equal(t, httpOS, os)
|
|
||||||
})
|
|
||||||
|
|
||||||
secondURL, err := url.Parse("https://127.0.0.1:8080")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
secondHTTPOS := originservice.NewHTTPService(http.DefaultTransport, secondURL, true)
|
|
||||||
concurrentOps(t, func(i int) {
|
|
||||||
// Add should httpOS with secondHTTPOS
|
|
||||||
thm.Add(tunnelHostname(i), secondHTTPOS)
|
|
||||||
})
|
|
||||||
|
|
||||||
concurrentOps(t, func(i int) {
|
|
||||||
os, ok := thm.Get(tunnelHostname(i))
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Equal(t, secondHTTPOS, os)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func concurrentOps(t *testing.T, f func(i int)) {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(routines)
|
|
||||||
for i := 0; i < routines; i++ {
|
|
||||||
go func(i int) {
|
|
||||||
f(i)
|
|
||||||
wg.Done()
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func tunnelHostname(i int) h2mux.TunnelHostname {
|
|
||||||
return h2mux.TunnelHostname(fmt.Sprintf("%d.cftunnel.com", i))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_toSet(t *testing.T) {
|
|
||||||
|
|
||||||
type args struct {
|
|
||||||
configs []*pogs.ReverseProxyConfig
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty slice should yield empty map",
|
|
||||||
args: args{},
|
|
||||||
want: map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multiple elements",
|
|
||||||
args: args{[]*pogs.ReverseProxyConfig{sampleConfig1(), sampleConfig2()}},
|
|
||||||
want: map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig{
|
|
||||||
"mock.example.com": sampleConfig1(),
|
|
||||||
"mock2.example.com": sampleConfig2(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := toSet(tt.args.configs); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("toSet() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTunnelHostnameMapper_ToAdd(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
tunnelHostnameToOrigin map[h2mux.TunnelHostname]originservice.OriginService
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
newConfigs []*pogs.ReverseProxyConfig
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
args args
|
|
||||||
wantToAdd []*pogs.ReverseProxyConfig
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Mapper={}, NewConfig={}, toAdd={}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Mapper={}, NewConfig={x}, toAdd={x}",
|
|
||||||
args: args{newConfigs: []*pogs.ReverseProxyConfig{sampleConfig1()}},
|
|
||||||
wantToAdd: []*pogs.ReverseProxyConfig{sampleConfig1()},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Mapper={x}, NewConfig={x,y}, toAdd={y}",
|
|
||||||
args: args{newConfigs: []*pogs.ReverseProxyConfig{sampleConfig2()}},
|
|
||||||
wantToAdd: []*pogs.ReverseProxyConfig{sampleConfig2()},
|
|
||||||
fields: fields{tunnelHostnameToOrigin: map[h2mux.TunnelHostname]originservice.OriginService{
|
|
||||||
h2mux.TunnelHostname(sampleConfig1().TunnelHostname): &originservice.HelloWorldService{},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
om := &TunnelHostnameMapper{
|
|
||||||
tunnelHostnameToOrigin: tt.fields.tunnelHostnameToOrigin,
|
|
||||||
}
|
|
||||||
if gotToAdd := om.ToAdd(tt.args.newConfigs); !reflect.DeepEqual(gotToAdd, tt.wantToAdd) {
|
|
||||||
t.Errorf("TunnelHostnameMapper.ToAdd() = %v, want %v", gotToAdd, tt.wantToAdd)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTunnelHostnameMapper_ToRemove(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
tunnelHostnameToOrigin map[h2mux.TunnelHostname]originservice.OriginService
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
newConfigs []*pogs.ReverseProxyConfig
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
args args
|
|
||||||
wantToRemove []h2mux.TunnelHostname
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Mapper={}, NewConfig={}, toRemove={}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Mapper={x}, NewConfig={}, toRemove={x}",
|
|
||||||
wantToRemove: []h2mux.TunnelHostname{sampleConfig1().TunnelHostname},
|
|
||||||
fields: fields{tunnelHostnameToOrigin: map[h2mux.TunnelHostname]originservice.OriginService{
|
|
||||||
h2mux.TunnelHostname(sampleConfig1().TunnelHostname): &originservice.HelloWorldService{},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Mapper={x}, NewConfig={x}, toRemove={}",
|
|
||||||
args: args{newConfigs: []*pogs.ReverseProxyConfig{sampleConfig1()}},
|
|
||||||
fields: fields{tunnelHostnameToOrigin: map[h2mux.TunnelHostname]originservice.OriginService{
|
|
||||||
h2mux.TunnelHostname(sampleConfig1().TunnelHostname): &originservice.HelloWorldService{},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
om := &TunnelHostnameMapper{
|
|
||||||
tunnelHostnameToOrigin: tt.fields.tunnelHostnameToOrigin,
|
|
||||||
}
|
|
||||||
if gotToRemove := om.ToRemove(tt.args.newConfigs); !reflect.DeepEqual(gotToRemove, tt.wantToRemove) {
|
|
||||||
t.Errorf("TunnelHostnameMapper.ToRemove() = %v, want %v", gotToRemove, tt.wantToRemove)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sampleConfig1() *pogs.ReverseProxyConfig {
|
|
||||||
return &pogs.ReverseProxyConfig{
|
|
||||||
TunnelHostname: "mock.example.com",
|
|
||||||
OriginConfig: &pogs.HelloWorldOriginConfig{},
|
|
||||||
Retries: 18,
|
|
||||||
ConnectionTimeout: 5 * time.Second,
|
|
||||||
CompressionQuality: 3,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sampleConfig2() *pogs.ReverseProxyConfig {
|
|
||||||
return &pogs.ReverseProxyConfig{
|
|
||||||
TunnelHostname: "mock2.example.com",
|
|
||||||
OriginConfig: &pogs.HelloWorldOriginConfig{},
|
|
||||||
Retries: 18,
|
|
||||||
ConnectionTimeout: 5 * time.Second,
|
|
||||||
CompressionQuality: 3,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,775 +0,0 @@
|
||||||
package pogs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
|
||||||
"github.com/cloudflare/cloudflared/originservice"
|
|
||||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
capnp "zombiezen.com/go/capnproto2"
|
|
||||||
"zombiezen.com/go/capnproto2/pogs"
|
|
||||||
"zombiezen.com/go/capnproto2/rpc"
|
|
||||||
"zombiezen.com/go/capnproto2/server"
|
|
||||||
)
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Structs
|
|
||||||
///
|
|
||||||
|
|
||||||
// ClientConfig is a collection of FallibleConfig that determines how cloudflared should function
|
|
||||||
type ClientConfig struct {
|
|
||||||
Version Version
|
|
||||||
SupervisorConfig *SupervisorConfig
|
|
||||||
EdgeConnectionConfig *EdgeConnectionConfig
|
|
||||||
DoHProxyConfigs []*DoHProxyConfig `capnp:"dohProxyConfigs"`
|
|
||||||
ReverseProxyConfigs []*ReverseProxyConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConfig) MarshalBytes() ([]byte, error) {
|
|
||||||
msg, firstSeg, err := capnp.NewMessage(capnp.SingleSegment(nil))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
capnpEntity, err := tunnelrpc.NewRootClientConfig(firstSeg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = MarshalClientConfig(capnpEntity, c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return msg.Marshal()
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalClientConfigFromBytes(clientConfigBytes []byte) (*ClientConfig, error) {
|
|
||||||
msg, err := capnp.Unmarshal(clientConfigBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
capnpClientConfig, err := tunnelrpc.ReadRootClientConfig(msg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pogsClientConfig, err := UnmarshalClientConfig(capnpClientConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return pogsClientConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version type models the version of a ClientConfig
|
|
||||||
type Version uint64
|
|
||||||
|
|
||||||
func InitVersion() Version {
|
|
||||||
return Version(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) IsNewerOrEqual(comparedVersion Version) bool {
|
|
||||||
return v >= comparedVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) String() string {
|
|
||||||
return fmt.Sprintf("Version: %d", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FallibleConfig is an interface implemented by configs that cloudflared might not be able to apply
|
|
||||||
//go-sumtype:decl FallibleConfig
|
|
||||||
type FallibleConfig interface {
|
|
||||||
FailReason(err error) string
|
|
||||||
isFallibleConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupervisorConfig specifies config of components managed by Supervisor other than ConnectionManager
|
|
||||||
type SupervisorConfig struct {
|
|
||||||
AutoUpdateFrequency time.Duration
|
|
||||||
MetricsUpdateFrequency time.Duration
|
|
||||||
GracePeriod time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// FailReason impelents FallibleConfig interface for SupervisorConfig
|
|
||||||
func (sc *SupervisorConfig) FailReason(err error) string {
|
|
||||||
return fmt.Sprintf("Cannot apply SupervisorConfig, err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ *SupervisorConfig) isFallibleConfig() {}
|
|
||||||
|
|
||||||
// EdgeConnectionConfig specifies what parameters and how may connections should ConnectionManager establish with edge
|
|
||||||
type EdgeConnectionConfig struct {
|
|
||||||
NumHAConnections uint8
|
|
||||||
HeartbeatInterval time.Duration
|
|
||||||
Timeout time.Duration
|
|
||||||
MaxFailedHeartbeats uint64
|
|
||||||
UserCredentialPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// FailReason impelents FallibleConfig interface for EdgeConnectionConfig
|
|
||||||
func (cmc *EdgeConnectionConfig) FailReason(err error) string {
|
|
||||||
return fmt.Sprintf("Cannot apply EdgeConnectionConfig, err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ *EdgeConnectionConfig) isFallibleConfig() {}
|
|
||||||
|
|
||||||
// DoHProxyConfig is configuration for DNS over HTTPS service
|
|
||||||
type DoHProxyConfig struct {
|
|
||||||
ListenHost string
|
|
||||||
ListenPort uint16
|
|
||||||
Upstreams []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// FailReason impelents FallibleConfig interface for DoHProxyConfig
|
|
||||||
func (dpc *DoHProxyConfig) FailReason(err error) string {
|
|
||||||
return fmt.Sprintf("Cannot apply DoHProxyConfig, err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ *DoHProxyConfig) isFallibleConfig() {}
|
|
||||||
|
|
||||||
// ReverseProxyConfig how and for what hostnames can this cloudflared proxy
|
|
||||||
type ReverseProxyConfig struct {
|
|
||||||
TunnelHostname h2mux.TunnelHostname
|
|
||||||
OriginConfig OriginConfig
|
|
||||||
Retries uint64
|
|
||||||
ConnectionTimeout time.Duration
|
|
||||||
CompressionQuality uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewReverseProxyConfig(
|
|
||||||
tunnelHostname string,
|
|
||||||
originConfig OriginConfig,
|
|
||||||
retries uint64,
|
|
||||||
connectionTimeout time.Duration,
|
|
||||||
compressionQuality uint64,
|
|
||||||
) (*ReverseProxyConfig, error) {
|
|
||||||
if originConfig == nil {
|
|
||||||
return nil, fmt.Errorf("NewReverseProxyConfig: originConfigUnmarshaler was null")
|
|
||||||
}
|
|
||||||
return &ReverseProxyConfig{
|
|
||||||
TunnelHostname: h2mux.TunnelHostname(tunnelHostname),
|
|
||||||
OriginConfig: originConfig,
|
|
||||||
Retries: retries,
|
|
||||||
ConnectionTimeout: connectionTimeout,
|
|
||||||
CompressionQuality: compressionQuality,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FailReason impelents FallibleConfig interface for ReverseProxyConfig
|
|
||||||
func (rpc *ReverseProxyConfig) FailReason(err error) string {
|
|
||||||
return fmt.Sprintf("Cannot apply ReverseProxyConfig, err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ *ReverseProxyConfig) isFallibleConfig() {}
|
|
||||||
|
|
||||||
//go-sumtype:decl OriginConfig
|
|
||||||
type OriginConfig interface {
|
|
||||||
// Service returns a OriginService used to proxy to the origin
|
|
||||||
Service(logger.Service) (originservice.OriginService, error)
|
|
||||||
// go-sumtype requires at least one unexported method, otherwise it will complain that interface is not sealed
|
|
||||||
isOriginConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
type HTTPOriginConfig struct {
|
|
||||||
URLString string `capnp:"urlString"`
|
|
||||||
TCPKeepAlive time.Duration `capnp:"tcpKeepAlive"`
|
|
||||||
DialDualStack bool
|
|
||||||
TLSHandshakeTimeout time.Duration `capnp:"tlsHandshakeTimeout"`
|
|
||||||
TLSVerify bool `capnp:"tlsVerify"`
|
|
||||||
OriginCAPool string
|
|
||||||
OriginServerName string
|
|
||||||
MaxIdleConnections uint64
|
|
||||||
IdleConnectionTimeout time.Duration
|
|
||||||
ProxyConnectionTimeout time.Duration
|
|
||||||
ExpectContinueTimeout time.Duration
|
|
||||||
ChunkedEncoding bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *HTTPOriginConfig) Service(logger logger.Service) (originservice.OriginService, error) {
|
|
||||||
rootCAs, err := tlsconfig.LoadCustomOriginCA(hc.OriginCAPool)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer := &net.Dialer{
|
|
||||||
Timeout: hc.ProxyConnectionTimeout,
|
|
||||||
KeepAlive: hc.TCPKeepAlive,
|
|
||||||
}
|
|
||||||
if !hc.DialDualStack {
|
|
||||||
dialer.FallbackDelay = -1
|
|
||||||
}
|
|
||||||
dialContext := dialer.DialContext
|
|
||||||
transport := &http.Transport{
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
DialContext: dialContext,
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
RootCAs: rootCAs,
|
|
||||||
ServerName: hc.OriginServerName,
|
|
||||||
InsecureSkipVerify: hc.TLSVerify,
|
|
||||||
},
|
|
||||||
TLSHandshakeTimeout: hc.TLSHandshakeTimeout,
|
|
||||||
MaxIdleConns: int(hc.MaxIdleConnections),
|
|
||||||
IdleConnTimeout: hc.IdleConnectionTimeout,
|
|
||||||
ExpectContinueTimeout: hc.ExpectContinueTimeout,
|
|
||||||
}
|
|
||||||
url, err := url.Parse(hc.URLString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "%s is not a valid URL", hc.URLString)
|
|
||||||
}
|
|
||||||
if url.Scheme == "unix" {
|
|
||||||
transport.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
|
|
||||||
return dialContext(ctx, "unix", url.Host)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return originservice.NewHTTPService(transport, url, hc.ChunkedEncoding), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*HTTPOriginConfig) isOriginConfig() {}
|
|
||||||
|
|
||||||
type WebSocketOriginConfig struct {
|
|
||||||
URLString string `capnp:"urlString"`
|
|
||||||
TLSVerify bool `capnp:"tlsVerify"`
|
|
||||||
OriginCAPool string
|
|
||||||
OriginServerName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wsc *WebSocketOriginConfig) Service(logger logger.Service) (originservice.OriginService, error) {
|
|
||||||
rootCAs, err := tlsconfig.LoadCustomOriginCA(wsc.OriginCAPool)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
RootCAs: rootCAs,
|
|
||||||
ServerName: wsc.OriginServerName,
|
|
||||||
InsecureSkipVerify: wsc.TLSVerify,
|
|
||||||
}
|
|
||||||
|
|
||||||
url, err := url.Parse(wsc.URLString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "%s is not a valid URL", wsc.URLString)
|
|
||||||
}
|
|
||||||
return originservice.NewWebSocketService(tlsConfig, url, logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*WebSocketOriginConfig) isOriginConfig() {}
|
|
||||||
|
|
||||||
type HelloWorldOriginConfig struct{}
|
|
||||||
|
|
||||||
func (*HelloWorldOriginConfig) Service(logger logger.Service) (originservice.OriginService, error) {
|
|
||||||
helloCert, err := tlsconfig.GetHelloCertificateX509()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Cannot get Hello World server certificate")
|
|
||||||
}
|
|
||||||
rootCAs := x509.NewCertPool()
|
|
||||||
rootCAs.AddCert(helloCert)
|
|
||||||
transport := &http.Transport{
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
DialContext: (&net.Dialer{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
KeepAlive: 30 * time.Second,
|
|
||||||
}).DialContext,
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
RootCAs: rootCAs,
|
|
||||||
},
|
|
||||||
MaxIdleConns: 100,
|
|
||||||
IdleConnTimeout: 90 * time.Second,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
|
||||||
}
|
|
||||||
return originservice.NewHelloWorldService(transport, logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*HelloWorldOriginConfig) isOriginConfig() {}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Boilerplate to convert between these structs and the primitive structs
|
|
||||||
* generated by capnp-go.
|
|
||||||
* Mnemonics for variable names in this section:
|
|
||||||
* - `p` is for POGS (plain old Go struct)
|
|
||||||
* - `s` (and `ss`) is for "capnp.Struct", which is the fundamental type
|
|
||||||
* underlying the capnp-go data structures.
|
|
||||||
*/
|
|
||||||
|
|
||||||
func MarshalClientConfig(s tunnelrpc.ClientConfig, p *ClientConfig) error {
|
|
||||||
s.SetVersion(uint64(p.Version))
|
|
||||||
|
|
||||||
supervisorConfig, err := s.NewSupervisorConfig()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to get SupervisorConfig")
|
|
||||||
}
|
|
||||||
if err = MarshalSupervisorConfig(supervisorConfig, p.SupervisorConfig); err != nil {
|
|
||||||
return errors.Wrap(err, "MarshalSupervisorConfig error")
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeConnectionConfig, err := s.NewEdgeConnectionConfig()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to get EdgeConnectionConfig")
|
|
||||||
}
|
|
||||||
if err := MarshalEdgeConnectionConfig(edgeConnectionConfig, p.EdgeConnectionConfig); err != nil {
|
|
||||||
return errors.Wrap(err, "MarshalEdgeConnectionConfig error")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := marshalDoHProxyConfigs(s, p.DoHProxyConfigs); err != nil {
|
|
||||||
return errors.Wrap(err, "marshalDoHProxyConfigs error")
|
|
||||||
}
|
|
||||||
if err := marshalReverseProxyConfigs(s, p.ReverseProxyConfigs); err != nil {
|
|
||||||
return errors.Wrap(err, "marshalReverseProxyConfigs error")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func MarshalSupervisorConfig(s tunnelrpc.SupervisorConfig, p *SupervisorConfig) error {
|
|
||||||
if err := pogs.Insert(tunnelrpc.SupervisorConfig_TypeID, s.Struct, p); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to insert SupervisorConfig")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func MarshalEdgeConnectionConfig(s tunnelrpc.EdgeConnectionConfig, p *EdgeConnectionConfig) error {
|
|
||||||
if err := pogs.Insert(tunnelrpc.EdgeConnectionConfig_TypeID, s.Struct, p); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to insert EdgeConnectionConfig")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalDoHProxyConfigs(s tunnelrpc.ClientConfig, dohProxyConfigs []*DoHProxyConfig) error {
|
|
||||||
capnpList, err := s.NewDohProxyConfigs(int32(len(dohProxyConfigs)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i, unmarshalledConfig := range dohProxyConfigs {
|
|
||||||
err := MarshalDoHProxyConfig(capnpList.At(i), unmarshalledConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalReverseProxyConfigs(s tunnelrpc.ClientConfig, reverseProxyConfigs []*ReverseProxyConfig) error {
|
|
||||||
capnpList, err := s.NewReverseProxyConfigs(int32(len(reverseProxyConfigs)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i, unmarshalledConfig := range reverseProxyConfigs {
|
|
||||||
err := MarshalReverseProxyConfig(capnpList.At(i), unmarshalledConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalClientConfig(s tunnelrpc.ClientConfig) (*ClientConfig, error) {
|
|
||||||
p := new(ClientConfig)
|
|
||||||
p.Version = Version(s.Version())
|
|
||||||
|
|
||||||
supervisorConfig, err := s.SupervisorConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to get SupervisorConfig")
|
|
||||||
}
|
|
||||||
p.SupervisorConfig, err = UnmarshalSupervisorConfig(supervisorConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "UnmarshalSupervisorConfig error")
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeConnectionConfig, err := s.EdgeConnectionConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to get ConnectionManagerConfig")
|
|
||||||
}
|
|
||||||
p.EdgeConnectionConfig, err = UnmarshalEdgeConnectionConfig(edgeConnectionConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "UnmarshalConnectionManagerConfig error")
|
|
||||||
}
|
|
||||||
|
|
||||||
p.DoHProxyConfigs, err = unmarshalDoHProxyConfigs(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "unmarshalDoHProxyConfigs error")
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ReverseProxyConfigs, err = unmarshalReverseProxyConfigs(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "unmarshalReverseProxyConfigs error")
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalSupervisorConfig(s tunnelrpc.SupervisorConfig) (*SupervisorConfig, error) {
|
|
||||||
p := new(SupervisorConfig)
|
|
||||||
err := pogs.Extract(p, tunnelrpc.SupervisorConfig_TypeID, s.Struct)
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalEdgeConnectionConfig(s tunnelrpc.EdgeConnectionConfig) (*EdgeConnectionConfig, error) {
|
|
||||||
p := new(EdgeConnectionConfig)
|
|
||||||
err := pogs.Extract(p, tunnelrpc.EdgeConnectionConfig_TypeID, s.Struct)
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalDoHProxyConfigs(s tunnelrpc.ClientConfig) ([]*DoHProxyConfig, error) {
|
|
||||||
var result []*DoHProxyConfig
|
|
||||||
marshalledDoHProxyConfigs, err := s.DohProxyConfigs()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for i := 0; i < marshalledDoHProxyConfigs.Len(); i++ {
|
|
||||||
ss := marshalledDoHProxyConfigs.At(i)
|
|
||||||
dohProxyConfig, err := UnmarshalDoHProxyConfig(ss)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result = append(result, dohProxyConfig)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalReverseProxyConfigs(s tunnelrpc.ClientConfig) ([]*ReverseProxyConfig, error) {
|
|
||||||
var result []*ReverseProxyConfig
|
|
||||||
marshalledReverseProxyConfigs, err := s.ReverseProxyConfigs()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for i := 0; i < marshalledReverseProxyConfigs.Len(); i++ {
|
|
||||||
ss := marshalledReverseProxyConfigs.At(i)
|
|
||||||
reverseProxyConfig, err := UnmarshalReverseProxyConfig(ss)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result = append(result, reverseProxyConfig)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func MarshalUseConfigurationResult(s tunnelrpc.UseConfigurationResult, p *UseConfigurationResult) error {
|
|
||||||
capnpList, err := s.NewFailedConfigs(int32(len(p.FailedConfigs)))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "Cannot create new FailedConfigs")
|
|
||||||
}
|
|
||||||
for i, unmarshalledFailedConfig := range p.FailedConfigs {
|
|
||||||
err := MarshalFailedConfig(capnpList.At(i), unmarshalledFailedConfig)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "Cannot MarshalFailedConfig at index %d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.SetSuccess(p.Success)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalUseConfigurationResult(s tunnelrpc.UseConfigurationResult) (*UseConfigurationResult, error) {
|
|
||||||
p := new(UseConfigurationResult)
|
|
||||||
var failedConfigs []*FailedConfig
|
|
||||||
marshalledFailedConfigs, err := s.FailedConfigs()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Cannot get FailedConfigs")
|
|
||||||
}
|
|
||||||
for i := 0; i < marshalledFailedConfigs.Len(); i++ {
|
|
||||||
ss := marshalledFailedConfigs.At(i)
|
|
||||||
failedConfig, err := UnmarshalFailedConfig(ss)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "Cannot UnmarshalFailedConfig at index %d", i)
|
|
||||||
}
|
|
||||||
failedConfigs = append(failedConfigs, failedConfig)
|
|
||||||
}
|
|
||||||
p.FailedConfigs = failedConfigs
|
|
||||||
p.Success = s.Success()
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func MarshalDoHProxyConfig(s tunnelrpc.DoHProxyConfig, p *DoHProxyConfig) error {
|
|
||||||
return pogs.Insert(tunnelrpc.DoHProxyConfig_TypeID, s.Struct, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalDoHProxyConfig(s tunnelrpc.DoHProxyConfig) (*DoHProxyConfig, error) {
|
|
||||||
p := new(DoHProxyConfig)
|
|
||||||
err := pogs.Extract(p, tunnelrpc.DoHProxyConfig_TypeID, s.Struct)
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func MarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig, p *ReverseProxyConfig) error {
|
|
||||||
s.SetTunnelHostname(p.TunnelHostname.String())
|
|
||||||
switch config := p.OriginConfig.(type) {
|
|
||||||
case *HTTPOriginConfig:
|
|
||||||
ss, err := s.OriginConfig().NewHttp()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := MarshalHTTPOriginConfig(ss, config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case *WebSocketOriginConfig:
|
|
||||||
ss, err := s.OriginConfig().NewWebsocket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := MarshalWebSocketOriginConfig(ss, config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case *HelloWorldOriginConfig:
|
|
||||||
ss, err := s.OriginConfig().NewHelloWorld()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := MarshalHelloWorldOriginConfig(ss, config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Unknown type for config: %T", config)
|
|
||||||
}
|
|
||||||
s.SetRetries(p.Retries)
|
|
||||||
s.SetConnectionTimeout(p.ConnectionTimeout.Nanoseconds())
|
|
||||||
s.SetCompressionQuality(p.CompressionQuality)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig) (*ReverseProxyConfig, error) {
|
|
||||||
p := new(ReverseProxyConfig)
|
|
||||||
tunnelHostname, err := s.TunnelHostname()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.TunnelHostname = h2mux.TunnelHostname(tunnelHostname)
|
|
||||||
switch s.OriginConfig().Which() {
|
|
||||||
case tunnelrpc.ReverseProxyConfig_originConfig_Which_http:
|
|
||||||
ss, err := s.OriginConfig().Http()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config, err := UnmarshalHTTPOriginConfig(ss)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.OriginConfig = config
|
|
||||||
case tunnelrpc.ReverseProxyConfig_originConfig_Which_websocket:
|
|
||||||
ss, err := s.OriginConfig().Websocket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config, err := UnmarshalWebSocketOriginConfig(ss)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.OriginConfig = config
|
|
||||||
case tunnelrpc.ReverseProxyConfig_originConfig_Which_helloWorld:
|
|
||||||
ss, err := s.OriginConfig().HelloWorld()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config, err := UnmarshalHelloWorldOriginConfig(ss)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.OriginConfig = config
|
|
||||||
}
|
|
||||||
p.Retries = s.Retries()
|
|
||||||
p.ConnectionTimeout = time.Duration(s.ConnectionTimeout())
|
|
||||||
p.CompressionQuality = s.CompressionQuality()
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func MarshalHTTPOriginConfig(s tunnelrpc.HTTPOriginConfig, p *HTTPOriginConfig) error {
|
|
||||||
return pogs.Insert(tunnelrpc.HTTPOriginConfig_TypeID, s.Struct, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalHTTPOriginConfig(s tunnelrpc.HTTPOriginConfig) (*HTTPOriginConfig, error) {
|
|
||||||
p := new(HTTPOriginConfig)
|
|
||||||
err := pogs.Extract(p, tunnelrpc.HTTPOriginConfig_TypeID, s.Struct)
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func MarshalWebSocketOriginConfig(s tunnelrpc.WebSocketOriginConfig, p *WebSocketOriginConfig) error {
|
|
||||||
return pogs.Insert(tunnelrpc.WebSocketOriginConfig_TypeID, s.Struct, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalWebSocketOriginConfig(s tunnelrpc.WebSocketOriginConfig) (*WebSocketOriginConfig, error) {
|
|
||||||
p := new(WebSocketOriginConfig)
|
|
||||||
err := pogs.Extract(p, tunnelrpc.WebSocketOriginConfig_TypeID, s.Struct)
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func MarshalHelloWorldOriginConfig(s tunnelrpc.HelloWorldOriginConfig, p *HelloWorldOriginConfig) error {
|
|
||||||
return pogs.Insert(tunnelrpc.HelloWorldOriginConfig_TypeID, s.Struct, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalHelloWorldOriginConfig(s tunnelrpc.HelloWorldOriginConfig) (*HelloWorldOriginConfig, error) {
|
|
||||||
p := new(HelloWorldOriginConfig)
|
|
||||||
err := pogs.Extract(p, tunnelrpc.HelloWorldOriginConfig_TypeID, s.Struct)
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientService interface {
|
|
||||||
UseConfiguration(ctx context.Context, config *ClientConfig) (*UseConfigurationResult, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientService_PogsClient struct {
|
|
||||||
Client capnp.Client
|
|
||||||
Conn *rpc.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientService_PogsClient) Close() error {
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientService_PogsClient) UseConfiguration(
|
|
||||||
ctx context.Context,
|
|
||||||
config *ClientConfig,
|
|
||||||
) (*UseConfigurationResult, error) {
|
|
||||||
client := tunnelrpc.ClientService{Client: c.Client}
|
|
||||||
promise := client.UseConfiguration(ctx, func(p tunnelrpc.ClientService_useConfiguration_Params) error {
|
|
||||||
clientServiceConfig, err := p.NewClientServiceConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return MarshalClientConfig(clientServiceConfig, config)
|
|
||||||
})
|
|
||||||
retval, err := promise.Result().Struct()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return UnmarshalUseConfigurationResult(retval)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClientService_ServerToClient(s ClientService) tunnelrpc.ClientService {
|
|
||||||
return tunnelrpc.ClientService_ServerToClient(ClientService_PogsImpl{s})
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientService_PogsImpl struct {
|
|
||||||
impl ClientService
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i ClientService_PogsImpl) UseConfiguration(p tunnelrpc.ClientService_useConfiguration) error {
|
|
||||||
config, err := p.Params.ClientServiceConfig()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "Cannot get CloudflaredConfig parameter")
|
|
||||||
}
|
|
||||||
pogsConfig, err := UnmarshalClientConfig(config)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "Cannot unmarshal tunnelrpc.CloudflaredConfig to *CloudflaredConfig")
|
|
||||||
}
|
|
||||||
server.Ack(p.Options)
|
|
||||||
userConfigResult, err := i.impl.UseConfiguration(p.Ctx, pogsConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
result, err := p.Results.NewResult()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return MarshalUseConfigurationResult(result, userConfigResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
type UseConfigurationResult struct {
|
|
||||||
Success bool
|
|
||||||
FailedConfigs []*FailedConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type FailedConfig struct {
|
|
||||||
Config FallibleConfig
|
|
||||||
Reason string
|
|
||||||
}
|
|
||||||
|
|
||||||
func MarshalFailedConfig(s tunnelrpc.FailedConfig, p *FailedConfig) error {
|
|
||||||
switch config := p.Config.(type) {
|
|
||||||
case *SupervisorConfig:
|
|
||||||
ss, err := s.Config().NewSupervisor()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = MarshalSupervisorConfig(ss, config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case *EdgeConnectionConfig:
|
|
||||||
ss, err := s.Config().NewEdgeConnection()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = MarshalEdgeConnectionConfig(ss, config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case *DoHProxyConfig:
|
|
||||||
ss, err := s.Config().NewDoh()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = MarshalDoHProxyConfig(ss, config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case *ReverseProxyConfig:
|
|
||||||
ss, err := s.Config().NewReverseProxy()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = MarshalReverseProxyConfig(ss, config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Unknown type for Config: %T", config)
|
|
||||||
}
|
|
||||||
s.SetReason(p.Reason)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalFailedConfig(s tunnelrpc.FailedConfig) (*FailedConfig, error) {
|
|
||||||
p := new(FailedConfig)
|
|
||||||
switch s.Config().Which() {
|
|
||||||
case tunnelrpc.FailedConfig_config_Which_supervisor:
|
|
||||||
ss, err := s.Config().Supervisor()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Cannot get SupervisorConfig from Config")
|
|
||||||
}
|
|
||||||
config, err := UnmarshalSupervisorConfig(ss)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Cannot UnmarshalSupervisorConfig")
|
|
||||||
}
|
|
||||||
p.Config = config
|
|
||||||
case tunnelrpc.FailedConfig_config_Which_edgeConnection:
|
|
||||||
ss, err := s.Config().EdgeConnection()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Cannot get ConnectionManager from Config")
|
|
||||||
}
|
|
||||||
config, err := UnmarshalEdgeConnectionConfig(ss)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Cannot UnmarshalConnectionManagerConfig")
|
|
||||||
}
|
|
||||||
p.Config = config
|
|
||||||
case tunnelrpc.FailedConfig_config_Which_doh:
|
|
||||||
ss, err := s.Config().Doh()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Cannot get Doh from Config")
|
|
||||||
}
|
|
||||||
config, err := UnmarshalDoHProxyConfig(ss)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Cannot UnmarshalDoHProxyConfig")
|
|
||||||
}
|
|
||||||
p.Config = config
|
|
||||||
case tunnelrpc.FailedConfig_config_Which_reverseProxy:
|
|
||||||
ss, err := s.Config().ReverseProxy()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Cannot get ReverseProxy from Config")
|
|
||||||
}
|
|
||||||
config, err := UnmarshalReverseProxyConfig(ss)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Cannot UnmarshalReverseProxyConfig")
|
|
||||||
}
|
|
||||||
p.Config = config
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Unknown type for FailedConfig: %v", s.Config().Which())
|
|
||||||
}
|
|
||||||
reason, err := s.Reason()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Cannot get Reason")
|
|
||||||
}
|
|
||||||
p.Reason = reason
|
|
||||||
return p, nil
|
|
||||||
}
|
|
|
@ -1,455 +0,0 @@
|
||||||
package pogs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
|
||||||
capnp "zombiezen.com/go/capnproto2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Assert *HTTPOriginConfig implements OriginConfig
|
|
||||||
var _ OriginConfig = (*HTTPOriginConfig)(nil)
|
|
||||||
|
|
||||||
// Assert *WebSocketOriginConfig implements OriginConfig
|
|
||||||
var _ OriginConfig = (*WebSocketOriginConfig)(nil)
|
|
||||||
|
|
||||||
// Assert *HelloWorldOriginConfig implements OriginConfig
|
|
||||||
var _ OriginConfig = (*HelloWorldOriginConfig)(nil)
|
|
||||||
|
|
||||||
func TestVersion(t *testing.T) {
|
|
||||||
firstVersion := InitVersion()
|
|
||||||
secondVersion := Version(1)
|
|
||||||
assert.False(t, firstVersion.IsNewerOrEqual(secondVersion))
|
|
||||||
assert.True(t, secondVersion.IsNewerOrEqual(firstVersion))
|
|
||||||
assert.True(t, secondVersion.IsNewerOrEqual(secondVersion))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClientConfigCapnp(t *testing.T) {
|
|
||||||
for i, testCase := range ClientConfigTestCases() {
|
|
||||||
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
|
|
||||||
capnpEntity, err := tunnelrpc.NewClientConfig(seg)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal("Couldn't initialize a new message")
|
|
||||||
}
|
|
||||||
err = MarshalClientConfig(capnpEntity, testCase)
|
|
||||||
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result, err := UnmarshalClientConfig(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClientConfigTestCases() []*ClientConfig {
|
|
||||||
|
|
||||||
addDoHProxyConfigs := func(c *ClientConfig) {
|
|
||||||
c.DoHProxyConfigs = []*DoHProxyConfig{
|
|
||||||
sampleDoHProxyConfig(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addReverseProxyConfigs := func(c *ClientConfig) {
|
|
||||||
c.ReverseProxyConfigs = []*ReverseProxyConfig{
|
|
||||||
sampleReverseProxyConfig(),
|
|
||||||
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
|
|
||||||
}),
|
|
||||||
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
|
|
||||||
c.OriginConfig = sampleHTTPOriginConfig()
|
|
||||||
}),
|
|
||||||
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
|
|
||||||
c.OriginConfig = sampleHTTPOriginConfigUnixPath()
|
|
||||||
}),
|
|
||||||
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
|
|
||||||
c.OriginConfig = sampleWebSocketOriginConfig()
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []*ClientConfig{
|
|
||||||
sampleClientConfig(),
|
|
||||||
sampleClientConfig(addDoHProxyConfigs),
|
|
||||||
sampleClientConfig(addReverseProxyConfigs),
|
|
||||||
sampleClientConfig(addDoHProxyConfigs, addReverseProxyConfigs),
|
|
||||||
}
|
|
||||||
|
|
||||||
return testCases
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClientConfig(t *testing.T) {
|
|
||||||
for _, testCase := range ClientConfigTestCases() {
|
|
||||||
b, err := testCase.MarshalBytes()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
clientConfig, err := UnmarshalClientConfigFromBytes(b)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase, clientConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUseConfigurationResult(t *testing.T) {
|
|
||||||
testCases := []*UseConfigurationResult{
|
|
||||||
&UseConfigurationResult{
|
|
||||||
Success: true,
|
|
||||||
},
|
|
||||||
&UseConfigurationResult{
|
|
||||||
Success: false,
|
|
||||||
FailedConfigs: []*FailedConfig{
|
|
||||||
{
|
|
||||||
Config: sampleReverseProxyConfig(),
|
|
||||||
Reason: "Invalid certificate",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Config: sampleDoHProxyConfig(),
|
|
||||||
Reason: "Cannot listen on port 53",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, testCase := range testCases {
|
|
||||||
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
|
|
||||||
capnpEntity, err := tunnelrpc.NewUseConfigurationResult(seg)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal("Couldn't initialize a new message")
|
|
||||||
}
|
|
||||||
err = MarshalUseConfigurationResult(capnpEntity, testCase)
|
|
||||||
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result, err := UnmarshalUseConfigurationResult(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDoHProxyConfig(t *testing.T) {
|
|
||||||
testCases := []*DoHProxyConfig{
|
|
||||||
sampleDoHProxyConfig(),
|
|
||||||
sampleDoHProxyConfig(func(c *DoHProxyConfig) {
|
|
||||||
c.Upstreams = nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
for i, testCase := range testCases {
|
|
||||||
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
|
|
||||||
capnpEntity, err := tunnelrpc.NewDoHProxyConfig(seg)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal("Couldn't initialize a new message")
|
|
||||||
}
|
|
||||||
err = MarshalDoHProxyConfig(capnpEntity, testCase)
|
|
||||||
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result, err := UnmarshalDoHProxyConfig(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReverseProxyConfig(t *testing.T) {
|
|
||||||
testCases := []*ReverseProxyConfig{
|
|
||||||
sampleReverseProxyConfig(),
|
|
||||||
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
|
|
||||||
c.OriginConfig = sampleHTTPOriginConfig()
|
|
||||||
}),
|
|
||||||
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
|
|
||||||
c.OriginConfig = sampleHTTPOriginConfigUnixPath()
|
|
||||||
}),
|
|
||||||
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
|
|
||||||
c.OriginConfig = sampleWebSocketOriginConfig()
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
for i, testCase := range testCases {
|
|
||||||
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
|
|
||||||
capnpEntity, err := tunnelrpc.NewReverseProxyConfig(seg)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal("Couldn't initialize a new message")
|
|
||||||
}
|
|
||||||
err = MarshalReverseProxyConfig(capnpEntity, testCase)
|
|
||||||
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result, err := UnmarshalReverseProxyConfig(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHTTPOriginConfig(t *testing.T) {
|
|
||||||
testCases := []*HTTPOriginConfig{
|
|
||||||
sampleHTTPOriginConfig(),
|
|
||||||
}
|
|
||||||
for i, testCase := range testCases {
|
|
||||||
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
|
|
||||||
capnpEntity, err := tunnelrpc.NewHTTPOriginConfig(seg)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal("Couldn't initialize a new message")
|
|
||||||
}
|
|
||||||
err = MarshalHTTPOriginConfig(capnpEntity, testCase)
|
|
||||||
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result, err := UnmarshalHTTPOriginConfig(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWebSocketOriginConfig(t *testing.T) {
|
|
||||||
testCases := []*WebSocketOriginConfig{
|
|
||||||
sampleWebSocketOriginConfig(),
|
|
||||||
}
|
|
||||||
for i, testCase := range testCases {
|
|
||||||
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
|
|
||||||
capnpEntity, err := tunnelrpc.NewWebSocketOriginConfig(seg)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal("Couldn't initialize a new message")
|
|
||||||
}
|
|
||||||
err = MarshalWebSocketOriginConfig(capnpEntity, testCase)
|
|
||||||
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result, err := UnmarshalWebSocketOriginConfig(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOriginConfigInvalidURL(t *testing.T) {
|
|
||||||
invalidConfigs := []OriginConfig{
|
|
||||||
&HTTPOriginConfig{
|
|
||||||
// this url doesn't have a scheme
|
|
||||||
URLString: "127.0.0.1:36192",
|
|
||||||
},
|
|
||||||
&WebSocketOriginConfig{
|
|
||||||
URLString: "127.0.0.1:36192",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
|
|
||||||
|
|
||||||
for _, config := range invalidConfigs {
|
|
||||||
service, err := config.Service(logger)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, service)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Functions to generate sample data for ease of testing
|
|
||||||
//
|
|
||||||
// There's one "sample" function per struct type. Each goes like this:
|
|
||||||
// 1. Initialize an instance of the relevant struct.
|
|
||||||
// 2. Ensure the instance has no zero-valued fields. (This catches the
|
|
||||||
// error-case where a field was added, but we forgot to add code to
|
|
||||||
// marshal/unmarshal this field in CapnProto.)
|
|
||||||
// 3. Apply one or more "override" functions (which accept a
|
|
||||||
// pointer-to-struct, so they can mutate the instance).
|
|
||||||
|
|
||||||
// sampleClientConfig initializes a new ClientConfig literal,
|
|
||||||
// applies any number of overrides to it, and returns it.
|
|
||||||
func sampleClientConfig(overrides ...func(*ClientConfig)) *ClientConfig {
|
|
||||||
sample := &ClientConfig{
|
|
||||||
Version: Version(1337),
|
|
||||||
SupervisorConfig: sampleSupervisorConfig(),
|
|
||||||
EdgeConnectionConfig: sampleEdgeConnectionConfig(),
|
|
||||||
}
|
|
||||||
sample.ensureNoZeroFields()
|
|
||||||
for _, f := range overrides {
|
|
||||||
f(sample)
|
|
||||||
}
|
|
||||||
return sample
|
|
||||||
}
|
|
||||||
|
|
||||||
func sampleSupervisorConfig() *SupervisorConfig {
|
|
||||||
sample := &SupervisorConfig{
|
|
||||||
AutoUpdateFrequency: 21 * time.Hour,
|
|
||||||
MetricsUpdateFrequency: 11 * time.Minute,
|
|
||||||
GracePeriod: 31 * time.Second,
|
|
||||||
}
|
|
||||||
sample.ensureNoZeroFields()
|
|
||||||
return sample
|
|
||||||
}
|
|
||||||
|
|
||||||
func sampleEdgeConnectionConfig() *EdgeConnectionConfig {
|
|
||||||
sample := &EdgeConnectionConfig{
|
|
||||||
NumHAConnections: 49,
|
|
||||||
HeartbeatInterval: 5 * time.Second,
|
|
||||||
Timeout: 9 * time.Second,
|
|
||||||
MaxFailedHeartbeats: 9001,
|
|
||||||
UserCredentialPath: "/Users/example/.cloudflared/cert.pem",
|
|
||||||
}
|
|
||||||
sample.ensureNoZeroFields()
|
|
||||||
return sample
|
|
||||||
}
|
|
||||||
|
|
||||||
// sampleDoHProxyConfig initializes a new DoHProxyConfig struct,
|
|
||||||
// applies any number of overrides to it, and returns it.
|
|
||||||
func sampleDoHProxyConfig(overrides ...func(*DoHProxyConfig)) *DoHProxyConfig {
|
|
||||||
sample := &DoHProxyConfig{
|
|
||||||
ListenHost: "127.0.0.1",
|
|
||||||
ListenPort: 53,
|
|
||||||
Upstreams: []string{"1.1.1.1", "1.0.0.1"},
|
|
||||||
}
|
|
||||||
sample.ensureNoZeroFields()
|
|
||||||
for _, f := range overrides {
|
|
||||||
f(sample)
|
|
||||||
}
|
|
||||||
return sample
|
|
||||||
}
|
|
||||||
|
|
||||||
// sampleReverseProxyConfig initializes a new ReverseProxyConfig struct,
|
|
||||||
// applies any number of overrides to it, and returns it.
|
|
||||||
func sampleReverseProxyConfig(overrides ...func(*ReverseProxyConfig)) *ReverseProxyConfig {
|
|
||||||
sample := &ReverseProxyConfig{
|
|
||||||
TunnelHostname: "mock-non-lb-tunnel.example.com",
|
|
||||||
OriginConfig: &HelloWorldOriginConfig{},
|
|
||||||
Retries: 18,
|
|
||||||
ConnectionTimeout: 5 * time.Second,
|
|
||||||
CompressionQuality: 3,
|
|
||||||
}
|
|
||||||
sample.ensureNoZeroFields()
|
|
||||||
for _, f := range overrides {
|
|
||||||
f(sample)
|
|
||||||
}
|
|
||||||
return sample
|
|
||||||
}
|
|
||||||
|
|
||||||
func sampleHTTPOriginConfig(overrides ...func(*HTTPOriginConfig)) *HTTPOriginConfig {
|
|
||||||
sample := &HTTPOriginConfig{
|
|
||||||
URLString: "https.example.com",
|
|
||||||
TCPKeepAlive: 7 * time.Second,
|
|
||||||
DialDualStack: true,
|
|
||||||
TLSHandshakeTimeout: 11 * time.Second,
|
|
||||||
TLSVerify: true,
|
|
||||||
OriginCAPool: "/etc/cert.pem",
|
|
||||||
OriginServerName: "secure.example.com",
|
|
||||||
MaxIdleConnections: 19,
|
|
||||||
IdleConnectionTimeout: 17 * time.Second,
|
|
||||||
ProxyConnectionTimeout: 15 * time.Second,
|
|
||||||
ExpectContinueTimeout: 21 * time.Second,
|
|
||||||
ChunkedEncoding: true,
|
|
||||||
}
|
|
||||||
sample.ensureNoZeroFields()
|
|
||||||
for _, f := range overrides {
|
|
||||||
f(sample)
|
|
||||||
}
|
|
||||||
return sample
|
|
||||||
}
|
|
||||||
|
|
||||||
func sampleHTTPOriginConfigUnixPath(overrides ...func(*HTTPOriginConfig)) *HTTPOriginConfig {
|
|
||||||
sample := &HTTPOriginConfig{
|
|
||||||
URLString: "unix:/var/lib/file.sock",
|
|
||||||
TCPKeepAlive: 7 * time.Second,
|
|
||||||
DialDualStack: true,
|
|
||||||
TLSHandshakeTimeout: 11 * time.Second,
|
|
||||||
TLSVerify: true,
|
|
||||||
OriginCAPool: "/etc/cert.pem",
|
|
||||||
OriginServerName: "secure.example.com",
|
|
||||||
MaxIdleConnections: 19,
|
|
||||||
IdleConnectionTimeout: 17 * time.Second,
|
|
||||||
ProxyConnectionTimeout: 15 * time.Second,
|
|
||||||
ExpectContinueTimeout: 21 * time.Second,
|
|
||||||
ChunkedEncoding: true,
|
|
||||||
}
|
|
||||||
sample.ensureNoZeroFields()
|
|
||||||
for _, f := range overrides {
|
|
||||||
f(sample)
|
|
||||||
}
|
|
||||||
return sample
|
|
||||||
}
|
|
||||||
|
|
||||||
func sampleWebSocketOriginConfig(overrides ...func(*WebSocketOriginConfig)) *WebSocketOriginConfig {
|
|
||||||
sample := &WebSocketOriginConfig{
|
|
||||||
URLString: "ssh://example.com",
|
|
||||||
TLSVerify: true,
|
|
||||||
OriginCAPool: "/etc/cert.pem",
|
|
||||||
OriginServerName: "secure.example.com",
|
|
||||||
}
|
|
||||||
sample.ensureNoZeroFields()
|
|
||||||
for _, f := range overrides {
|
|
||||||
f(sample)
|
|
||||||
}
|
|
||||||
return sample
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConfig) ensureNoZeroFields() {
|
|
||||||
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{"DoHProxyConfigs", "ReverseProxyConfigs"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SupervisorConfig) ensureNoZeroFields() {
|
|
||||||
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *EdgeConnectionConfig) ensureNoZeroFields() {
|
|
||||||
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *DoHProxyConfig) ensureNoZeroFields() {
|
|
||||||
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ReverseProxyConfig) ensureNoZeroFields() {
|
|
||||||
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *HTTPOriginConfig) ensureNoZeroFields() {
|
|
||||||
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *WebSocketOriginConfig) ensureNoZeroFields() {
|
|
||||||
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensureNoZeroFieldsInSample checks that all fields in the sample struct,
|
|
||||||
// except those listed in `allowedZeroFieldNames`, are initialized to nonzero
|
|
||||||
// values. Note that the value has to be a pointer for reflection to work
|
|
||||||
// correctly:
|
|
||||||
// e := &ExampleStruct{ ... }
|
|
||||||
// ensureNoZeroFieldsInSample(reflect.ValueOf(e), []string{})
|
|
||||||
//
|
|
||||||
// Context:
|
|
||||||
// Our tests work by taking a sample struct and marshalling/unmarshalling it.
|
|
||||||
// This makes them easy to write, but introduces some risk: if we don't
|
|
||||||
// include a field in the sample value, it won't be covered under tests.
|
|
||||||
// This check reduces that risk by requiring fields to be either initialized
|
|
||||||
// or explicitly uninitialized.
|
|
||||||
func ensureNoZeroFieldsInSample(ptrToSampleValue reflect.Value, allowedZeroFieldNames []string) {
|
|
||||||
sampleValue := ptrToSampleValue.Elem()
|
|
||||||
structType := ptrToSampleValue.Type().Elem()
|
|
||||||
|
|
||||||
allowedZeroFieldSet := make(map[string]bool)
|
|
||||||
for _, name := range allowedZeroFieldNames {
|
|
||||||
if _, ok := structType.FieldByName(name); !ok {
|
|
||||||
panic(fmt.Sprintf("struct %v has no field %v", structType.Name(), name))
|
|
||||||
}
|
|
||||||
allowedZeroFieldSet[name] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < structType.NumField(); i++ {
|
|
||||||
if allowedZeroFieldSet[structType.Field(i).Name] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
zeroValue := reflect.Zero(structType.Field(i).Type)
|
|
||||||
if reflect.DeepEqual(zeroValue.Interface(), sampleValue.Field(i).Interface()) {
|
|
||||||
panic(fmt.Sprintf("In the sample value for struct %v, field %v was not initialized", structType.Name(), structType.Field(i).Name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,11 +3,8 @@ package pogs
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
capnp "zombiezen.com/go/capnproto2"
|
capnp "zombiezen.com/go/capnproto2"
|
||||||
"zombiezen.com/go/capnproto2/pogs"
|
"zombiezen.com/go/capnproto2/pogs"
|
||||||
|
@ -184,143 +181,6 @@ func UnmarshalRegistrationOptions(s tunnelrpc.RegistrationOptions) (*Registratio
|
||||||
return p, err
|
return p, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectResult models the result of Connect RPC, implemented by ConnectError and ConnectSuccess.
|
|
||||||
type ConnectResult interface {
|
|
||||||
ConnectError() *ConnectError
|
|
||||||
ConnectedTo() string
|
|
||||||
ClientConfig() *ClientConfig
|
|
||||||
Marshal(s tunnelrpc.ConnectResult) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func MarshalConnectResult(s tunnelrpc.ConnectResult, p ConnectResult) error {
|
|
||||||
return p.Marshal(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalConnectResult(s tunnelrpc.ConnectResult) (ConnectResult, error) {
|
|
||||||
switch s.Result().Which() {
|
|
||||||
case tunnelrpc.ConnectResult_result_Which_err:
|
|
||||||
capnpConnectError, err := s.Result().Err()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return UnmarshalConnectError(capnpConnectError)
|
|
||||||
case tunnelrpc.ConnectResult_result_Which_success:
|
|
||||||
capnpConnectSuccess, err := s.Result().Success()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return UnmarshalConnectSuccess(capnpConnectSuccess)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Unmarshal %v not implemented yet", s.Result().Which().String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectSuccess is the concrete returned type when Connect RPC succeed
|
|
||||||
type ConnectSuccess struct {
|
|
||||||
ServerLocationName string
|
|
||||||
Config *ClientConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*ConnectSuccess) ConnectError() *ConnectError {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *ConnectSuccess) ConnectedTo() string {
|
|
||||||
return cs.ServerLocationName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *ConnectSuccess) ClientConfig() *ClientConfig {
|
|
||||||
return cs.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *ConnectSuccess) Marshal(s tunnelrpc.ConnectResult) error {
|
|
||||||
capnpConnectSuccess, err := s.Result().NewSuccess()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = capnpConnectSuccess.SetServerLocationName(cs.ServerLocationName)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to set ConnectSuccess.ServerLocationName")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cs.Config != nil {
|
|
||||||
capnpClientConfig, err := capnpConnectSuccess.NewClientConfig()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to initialize ConnectSuccess.ClientConfig")
|
|
||||||
}
|
|
||||||
if err := MarshalClientConfig(capnpClientConfig, cs.Config); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to marshal ClientConfig")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalConnectSuccess(s tunnelrpc.ConnectSuccess) (*ConnectSuccess, error) {
|
|
||||||
p := new(ConnectSuccess)
|
|
||||||
|
|
||||||
serverLocationName, err := s.ServerLocationName()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to get tunnelrpc.ConnectSuccess.ServerLocationName")
|
|
||||||
}
|
|
||||||
p.ServerLocationName = serverLocationName
|
|
||||||
|
|
||||||
if s.HasClientConfig() {
|
|
||||||
capnpClientConfig, err := s.ClientConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to get tunnelrpc.ConnectSuccess.ClientConfig")
|
|
||||||
}
|
|
||||||
p.Config, err = UnmarshalClientConfig(capnpClientConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to get unmarshal ClientConfig")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectError is the concrete returned type when Connect RPC encounters some error
|
|
||||||
type ConnectError struct {
|
|
||||||
Cause string
|
|
||||||
RetryAfter time.Duration
|
|
||||||
ShouldRetry bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ce *ConnectError) ConnectError() *ConnectError {
|
|
||||||
return ce
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*ConnectError) ConnectedTo() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*ConnectError) ClientConfig() *ClientConfig {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ce *ConnectError) Marshal(s tunnelrpc.ConnectResult) error {
|
|
||||||
capnpConnectError, err := s.Result().NewErr()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return MarshalConnectError(capnpConnectError, ce)
|
|
||||||
}
|
|
||||||
|
|
||||||
func MarshalConnectError(s tunnelrpc.ConnectError, p *ConnectError) error {
|
|
||||||
return pogs.Insert(tunnelrpc.ConnectError_TypeID, s.Struct, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalConnectError(s tunnelrpc.ConnectError) (*ConnectError, error) {
|
|
||||||
p := new(ConnectError)
|
|
||||||
err := pogs.Extract(p, tunnelrpc.ConnectError_TypeID, s.Struct)
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ConnectError) Error() string {
|
|
||||||
return e.Cause
|
|
||||||
}
|
|
||||||
|
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
|
@ -340,102 +200,10 @@ func UnmarshalServerInfo(s tunnelrpc.ServerInfo) (*ServerInfo, error) {
|
||||||
return p, err
|
return p, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectParameters struct {
|
|
||||||
OriginCert []byte
|
|
||||||
CloudflaredID uuid.UUID
|
|
||||||
NumPreviousAttempts uint8
|
|
||||||
Tags []Tag
|
|
||||||
CloudflaredVersion string
|
|
||||||
IntentLabel string
|
|
||||||
}
|
|
||||||
|
|
||||||
func MarshalConnectParameters(s tunnelrpc.CapnpConnectParameters, p *ConnectParameters) error {
|
|
||||||
if err := s.SetOriginCert(p.OriginCert); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cloudflaredIDBytes, err := p.CloudflaredID.MarshalBinary()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.SetCloudflaredID(cloudflaredIDBytes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.SetNumPreviousAttempts(p.NumPreviousAttempts)
|
|
||||||
if len(p.Tags) > 0 {
|
|
||||||
tagsCapnpList, err := s.NewTags(int32(len(p.Tags)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i, tag := range p.Tags {
|
|
||||||
tagCapnp := tagsCapnpList.At(i)
|
|
||||||
if err := tagCapnp.SetName(tag.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := tagCapnp.SetValue(tag.Value); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := s.SetCloudflaredVersion(p.CloudflaredVersion); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.SetIntentLabel(p.IntentLabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalConnectParameters(s tunnelrpc.CapnpConnectParameters) (*ConnectParameters, error) {
|
|
||||||
originCert, err := s.OriginCert()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cloudflaredIDBytes, err := s.CloudflaredID()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cloudflaredID, err := uuid.FromBytes(cloudflaredIDBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tagsCapnpList, err := s.Tags()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var tags []Tag
|
|
||||||
for i := 0; i < tagsCapnpList.Len(); i++ {
|
|
||||||
tagCapnp := tagsCapnpList.At(i)
|
|
||||||
name, err := tagCapnp.Name()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
value, err := tagCapnp.Value()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tags = append(tags, Tag{Name: name, Value: value})
|
|
||||||
}
|
|
||||||
|
|
||||||
cloudflaredVersion, err := s.CloudflaredVersion()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
intentLabel, err := s.IntentLabel()
|
|
||||||
return &ConnectParameters{
|
|
||||||
OriginCert: originCert,
|
|
||||||
CloudflaredID: cloudflaredID,
|
|
||||||
NumPreviousAttempts: s.NumPreviousAttempts(),
|
|
||||||
Tags: tags,
|
|
||||||
CloudflaredVersion: cloudflaredVersion,
|
|
||||||
IntentLabel: intentLabel,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TunnelServer interface {
|
type TunnelServer interface {
|
||||||
RegisterTunnel(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) *TunnelRegistration
|
RegisterTunnel(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) *TunnelRegistration
|
||||||
GetServerInfo(ctx context.Context) (*ServerInfo, error)
|
GetServerInfo(ctx context.Context) (*ServerInfo, error)
|
||||||
UnregisterTunnel(ctx context.Context, gracePeriodNanoSec int64) error
|
UnregisterTunnel(ctx context.Context, gracePeriodNanoSec int64) error
|
||||||
Connect(ctx context.Context, parameters *ConnectParameters) (ConnectResult, error)
|
|
||||||
Authenticate(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) (*AuthenticateResponse, 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)
|
ReconnectTunnel(ctx context.Context, jwt, eventDigest, connDigest []byte, hostname string, options *RegistrationOptions) (*TunnelRegistration, error)
|
||||||
}
|
}
|
||||||
|
@ -494,25 +262,8 @@ func (i TunnelServer_PogsImpl) UnregisterTunnel(p tunnelrpc.TunnelServer_unregis
|
||||||
return i.impl.UnregisterTunnel(p.Ctx, gracePeriodNanoSec)
|
return i.impl.UnregisterTunnel(p.Ctx, gracePeriodNanoSec)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i TunnelServer_PogsImpl) Connect(p tunnelrpc.TunnelServer_connect) error {
|
func (i TunnelServer_PogsImpl) ObsoleteDeclarativeTunnelConnect(p tunnelrpc.TunnelServer_obsoleteDeclarativeTunnelConnect) error {
|
||||||
parameters, err := p.Params.Parameters()
|
return fmt.Errorf("RPC to create declarative tunnel connection has been deprecated")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pogsParameters, err := UnmarshalConnectParameters(parameters)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
server.Ack(p.Options)
|
|
||||||
connectResult, err := i.impl.Connect(p.Ctx, pogsParameters)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
result, err := p.Results.NewResult()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return connectResult.Marshal(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunnelServer_PogsClient struct {
|
type TunnelServer_PogsClient struct {
|
||||||
|
@ -578,25 +329,3 @@ func (c TunnelServer_PogsClient) UnregisterTunnel(ctx context.Context, gracePeri
|
||||||
_, err := promise.Struct()
|
_, err := promise.Struct()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c TunnelServer_PogsClient) Connect(ctx context.Context,
|
|
||||||
parameters *ConnectParameters,
|
|
||||||
) (ConnectResult, error) {
|
|
||||||
client := tunnelrpc.TunnelServer{Client: c.Client}
|
|
||||||
promise := client.Connect(ctx, func(p tunnelrpc.TunnelServer_connect_Params) error {
|
|
||||||
connectParameters, err := p.NewParameters()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = MarshalConnectParameters(connectParameters, parameters)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
retval, err := promise.Result().Struct()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return UnmarshalConnectResult(retval)
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,12 +2,9 @@ package pogs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
capnp "zombiezen.com/go/capnproto2"
|
capnp "zombiezen.com/go/capnproto2"
|
||||||
)
|
)
|
||||||
|
@ -56,94 +53,3 @@ func TestTunnelRegistration(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConnectResult(t *testing.T) {
|
|
||||||
testCases := []ConnectResult{
|
|
||||||
&ConnectError{
|
|
||||||
Cause: "it broke",
|
|
||||||
ShouldRetry: false,
|
|
||||||
RetryAfter: 2 * time.Second,
|
|
||||||
},
|
|
||||||
&ConnectSuccess{
|
|
||||||
ServerLocationName: "SFO",
|
|
||||||
Config: sampleClientConfig(),
|
|
||||||
},
|
|
||||||
&ConnectSuccess{
|
|
||||||
ServerLocationName: "",
|
|
||||||
Config: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, testCase := range testCases {
|
|
||||||
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
|
|
||||||
capnpEntity, err := tunnelrpc.NewConnectResult(seg)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal("Couldn't initialize a new message")
|
|
||||||
}
|
|
||||||
err = MarshalConnectResult(capnpEntity, testCase)
|
|
||||||
if !assert.NoError(t, err, "testCase #%v failed to marshal", i) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result, err := UnmarshalConnectResult(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConnectParameters(t *testing.T) {
|
|
||||||
testCases := []*ConnectParameters{
|
|
||||||
sampleConnectParameters(),
|
|
||||||
sampleConnectParameters(func(c *ConnectParameters) {
|
|
||||||
c.IntentLabel = "my_intent"
|
|
||||||
}),
|
|
||||||
sampleConnectParameters(func(c *ConnectParameters) {
|
|
||||||
c.Tags = nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
for i, testCase := range testCases {
|
|
||||||
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
|
|
||||||
capnpEntity, err := tunnelrpc.NewCapnpConnectParameters(seg)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal("Couldn't initialize a new message")
|
|
||||||
}
|
|
||||||
err = MarshalConnectParameters(capnpEntity, testCase)
|
|
||||||
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result, err := UnmarshalConnectParameters(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sampleConnectParameters(overrides ...func(*ConnectParameters)) *ConnectParameters {
|
|
||||||
cloudflaredID, err := uuid.Parse("ED7BA470-8E54-465E-825C-99712043E01C")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
sample := &ConnectParameters{
|
|
||||||
OriginCert: []byte("my-origin-cert"),
|
|
||||||
CloudflaredID: cloudflaredID,
|
|
||||||
NumPreviousAttempts: 19,
|
|
||||||
Tags: []Tag{
|
|
||||||
Tag{
|
|
||||||
Name: "provision-method",
|
|
||||||
Value: "new",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CloudflaredVersion: "7.0",
|
|
||||||
IntentLabel: "my_intent",
|
|
||||||
}
|
|
||||||
sample.ensureNoZeroFields()
|
|
||||||
for _, f := range overrides {
|
|
||||||
f(sample)
|
|
||||||
}
|
|
||||||
return sample
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnectParameters) ensureNoZeroFields() {
|
|
||||||
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
|
|
||||||
}
|
|
||||||
|
|
|
@ -295,7 +295,8 @@ interface TunnelServer {
|
||||||
registerTunnel @0 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
|
registerTunnel @0 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
|
||||||
getServerInfo @1 () -> (result :ServerInfo);
|
getServerInfo @1 () -> (result :ServerInfo);
|
||||||
unregisterTunnel @2 (gracePeriodNanoSec :Int64) -> ();
|
unregisterTunnel @2 (gracePeriodNanoSec :Int64) -> ();
|
||||||
connect @3 (parameters :CapnpConnectParameters) -> (result :ConnectResult);
|
# obsoleteDeclarativeTunnelConnect RPC deprecated in TUN-3019
|
||||||
|
obsoleteDeclarativeTunnelConnect @3 (parameters :CapnpConnectParameters) -> (result :ConnectResult);
|
||||||
authenticate @4 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :AuthenticateResponse);
|
authenticate @4 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :AuthenticateResponse);
|
||||||
reconnectTunnel @5 (jwt :Data, eventDigest :Data, connDigest :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
|
reconnectTunnel @5 (jwt :Data, eventDigest :Data, connDigest :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2874,9 +2874,9 @@ func (c TunnelServer) UnregisterTunnel(ctx context.Context, params func(TunnelSe
|
||||||
}
|
}
|
||||||
return TunnelServer_unregisterTunnel_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
|
return TunnelServer_unregisterTunnel_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
|
||||||
}
|
}
|
||||||
func (c TunnelServer) Connect(ctx context.Context, params func(TunnelServer_connect_Params) error, opts ...capnp.CallOption) TunnelServer_connect_Results_Promise {
|
func (c TunnelServer) ObsoleteDeclarativeTunnelConnect(ctx context.Context, params func(TunnelServer_obsoleteDeclarativeTunnelConnect_Params) error, opts ...capnp.CallOption) TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise {
|
||||||
if c.Client == nil {
|
if c.Client == nil {
|
||||||
return TunnelServer_connect_Results_Promise{Pipeline: capnp.NewPipeline(capnp.ErrorAnswer(capnp.ErrNullClient))}
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise{Pipeline: capnp.NewPipeline(capnp.ErrorAnswer(capnp.ErrNullClient))}
|
||||||
}
|
}
|
||||||
call := &capnp.Call{
|
call := &capnp.Call{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
|
@ -2884,15 +2884,17 @@ func (c TunnelServer) Connect(ctx context.Context, params func(TunnelServer_conn
|
||||||
InterfaceID: 0xea58385c65416035,
|
InterfaceID: 0xea58385c65416035,
|
||||||
MethodID: 3,
|
MethodID: 3,
|
||||||
InterfaceName: "tunnelrpc/tunnelrpc.capnp:TunnelServer",
|
InterfaceName: "tunnelrpc/tunnelrpc.capnp:TunnelServer",
|
||||||
MethodName: "connect",
|
MethodName: "obsoleteDeclarativeTunnelConnect",
|
||||||
},
|
},
|
||||||
Options: capnp.NewCallOptions(opts),
|
Options: capnp.NewCallOptions(opts),
|
||||||
}
|
}
|
||||||
if params != nil {
|
if params != nil {
|
||||||
call.ParamsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 1}
|
call.ParamsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 1}
|
||||||
call.ParamsFunc = func(s capnp.Struct) error { return params(TunnelServer_connect_Params{Struct: s}) }
|
call.ParamsFunc = func(s capnp.Struct) error {
|
||||||
|
return params(TunnelServer_obsoleteDeclarativeTunnelConnect_Params{Struct: s})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return TunnelServer_connect_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
|
||||||
}
|
}
|
||||||
func (c TunnelServer) Authenticate(ctx context.Context, params func(TunnelServer_authenticate_Params) error, opts ...capnp.CallOption) TunnelServer_authenticate_Results_Promise {
|
func (c TunnelServer) Authenticate(ctx context.Context, params func(TunnelServer_authenticate_Params) error, opts ...capnp.CallOption) TunnelServer_authenticate_Results_Promise {
|
||||||
if c.Client == nil {
|
if c.Client == nil {
|
||||||
|
@ -2942,7 +2944,7 @@ type TunnelServer_Server interface {
|
||||||
|
|
||||||
UnregisterTunnel(TunnelServer_unregisterTunnel) error
|
UnregisterTunnel(TunnelServer_unregisterTunnel) error
|
||||||
|
|
||||||
Connect(TunnelServer_connect) error
|
ObsoleteDeclarativeTunnelConnect(TunnelServer_obsoleteDeclarativeTunnelConnect) error
|
||||||
|
|
||||||
Authenticate(TunnelServer_authenticate) error
|
Authenticate(TunnelServer_authenticate) error
|
||||||
|
|
||||||
|
@ -3006,11 +3008,11 @@ func TunnelServer_Methods(methods []server.Method, s TunnelServer_Server) []serv
|
||||||
InterfaceID: 0xea58385c65416035,
|
InterfaceID: 0xea58385c65416035,
|
||||||
MethodID: 3,
|
MethodID: 3,
|
||||||
InterfaceName: "tunnelrpc/tunnelrpc.capnp:TunnelServer",
|
InterfaceName: "tunnelrpc/tunnelrpc.capnp:TunnelServer",
|
||||||
MethodName: "connect",
|
MethodName: "obsoleteDeclarativeTunnelConnect",
|
||||||
},
|
},
|
||||||
Impl: func(c context.Context, opts capnp.CallOptions, p, r capnp.Struct) error {
|
Impl: func(c context.Context, opts capnp.CallOptions, p, r capnp.Struct) error {
|
||||||
call := TunnelServer_connect{c, opts, TunnelServer_connect_Params{Struct: p}, TunnelServer_connect_Results{Struct: r}}
|
call := TunnelServer_obsoleteDeclarativeTunnelConnect{c, opts, TunnelServer_obsoleteDeclarativeTunnelConnect_Params{Struct: p}, TunnelServer_obsoleteDeclarativeTunnelConnect_Results{Struct: r}}
|
||||||
return s.Connect(call)
|
return s.ObsoleteDeclarativeTunnelConnect(call)
|
||||||
},
|
},
|
||||||
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 1},
|
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 1},
|
||||||
})
|
})
|
||||||
|
@ -3070,12 +3072,12 @@ type TunnelServer_unregisterTunnel struct {
|
||||||
Results TunnelServer_unregisterTunnel_Results
|
Results TunnelServer_unregisterTunnel_Results
|
||||||
}
|
}
|
||||||
|
|
||||||
// TunnelServer_connect holds the arguments for a server call to TunnelServer.connect.
|
// TunnelServer_obsoleteDeclarativeTunnelConnect holds the arguments for a server call to TunnelServer.obsoleteDeclarativeTunnelConnect.
|
||||||
type TunnelServer_connect struct {
|
type TunnelServer_obsoleteDeclarativeTunnelConnect struct {
|
||||||
Ctx context.Context
|
Ctx context.Context
|
||||||
Options capnp.CallOptions
|
Options capnp.CallOptions
|
||||||
Params TunnelServer_connect_Params
|
Params TunnelServer_obsoleteDeclarativeTunnelConnect_Params
|
||||||
Results TunnelServer_connect_Results
|
Results TunnelServer_obsoleteDeclarativeTunnelConnect_Results
|
||||||
}
|
}
|
||||||
|
|
||||||
// TunnelServer_authenticate holds the arguments for a server call to TunnelServer.authenticate.
|
// TunnelServer_authenticate holds the arguments for a server call to TunnelServer.authenticate.
|
||||||
|
@ -3552,48 +3554,48 @@ func (p TunnelServer_unregisterTunnel_Results_Promise) Struct() (TunnelServer_un
|
||||||
return TunnelServer_unregisterTunnel_Results{s}, err
|
return TunnelServer_unregisterTunnel_Results{s}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunnelServer_connect_Params struct{ capnp.Struct }
|
type TunnelServer_obsoleteDeclarativeTunnelConnect_Params struct{ capnp.Struct }
|
||||||
|
|
||||||
// TunnelServer_connect_Params_TypeID is the unique identifier for the type TunnelServer_connect_Params.
|
// TunnelServer_obsoleteDeclarativeTunnelConnect_Params_TypeID is the unique identifier for the type TunnelServer_obsoleteDeclarativeTunnelConnect_Params.
|
||||||
const TunnelServer_connect_Params_TypeID = 0xa766b24d4fe5da35
|
const TunnelServer_obsoleteDeclarativeTunnelConnect_Params_TypeID = 0xa766b24d4fe5da35
|
||||||
|
|
||||||
func NewTunnelServer_connect_Params(s *capnp.Segment) (TunnelServer_connect_Params, error) {
|
func NewTunnelServer_obsoleteDeclarativeTunnelConnect_Params(s *capnp.Segment) (TunnelServer_obsoleteDeclarativeTunnelConnect_Params, error) {
|
||||||
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
|
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
|
||||||
return TunnelServer_connect_Params{st}, err
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Params{st}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRootTunnelServer_connect_Params(s *capnp.Segment) (TunnelServer_connect_Params, error) {
|
func NewRootTunnelServer_obsoleteDeclarativeTunnelConnect_Params(s *capnp.Segment) (TunnelServer_obsoleteDeclarativeTunnelConnect_Params, error) {
|
||||||
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
|
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
|
||||||
return TunnelServer_connect_Params{st}, err
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Params{st}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadRootTunnelServer_connect_Params(msg *capnp.Message) (TunnelServer_connect_Params, error) {
|
func ReadRootTunnelServer_obsoleteDeclarativeTunnelConnect_Params(msg *capnp.Message) (TunnelServer_obsoleteDeclarativeTunnelConnect_Params, error) {
|
||||||
root, err := msg.RootPtr()
|
root, err := msg.RootPtr()
|
||||||
return TunnelServer_connect_Params{root.Struct()}, err
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Params{root.Struct()}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Params) String() string {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params) String() string {
|
||||||
str, _ := text.Marshal(0xa766b24d4fe5da35, s.Struct)
|
str, _ := text.Marshal(0xa766b24d4fe5da35, s.Struct)
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Params) Parameters() (CapnpConnectParameters, error) {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params) Parameters() (CapnpConnectParameters, error) {
|
||||||
p, err := s.Struct.Ptr(0)
|
p, err := s.Struct.Ptr(0)
|
||||||
return CapnpConnectParameters{Struct: p.Struct()}, err
|
return CapnpConnectParameters{Struct: p.Struct()}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Params) HasParameters() bool {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params) HasParameters() bool {
|
||||||
p, err := s.Struct.Ptr(0)
|
p, err := s.Struct.Ptr(0)
|
||||||
return p.IsValid() || err != nil
|
return p.IsValid() || err != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Params) SetParameters(v CapnpConnectParameters) error {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params) SetParameters(v CapnpConnectParameters) error {
|
||||||
return s.Struct.SetPtr(0, v.Struct.ToPtr())
|
return s.Struct.SetPtr(0, v.Struct.ToPtr())
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewParameters sets the parameters field to a newly
|
// NewParameters sets the parameters field to a newly
|
||||||
// allocated CapnpConnectParameters struct, preferring placement in s's segment.
|
// allocated CapnpConnectParameters struct, preferring placement in s's segment.
|
||||||
func (s TunnelServer_connect_Params) NewParameters() (CapnpConnectParameters, error) {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params) NewParameters() (CapnpConnectParameters, error) {
|
||||||
ss, err := NewCapnpConnectParameters(s.Struct.Segment())
|
ss, err := NewCapnpConnectParameters(s.Struct.Segment())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CapnpConnectParameters{}, err
|
return CapnpConnectParameters{}, err
|
||||||
|
@ -3602,82 +3604,82 @@ func (s TunnelServer_connect_Params) NewParameters() (CapnpConnectParameters, er
|
||||||
return ss, err
|
return ss, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TunnelServer_connect_Params_List is a list of TunnelServer_connect_Params.
|
// TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List is a list of TunnelServer_obsoleteDeclarativeTunnelConnect_Params.
|
||||||
type TunnelServer_connect_Params_List struct{ capnp.List }
|
type TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List struct{ capnp.List }
|
||||||
|
|
||||||
// NewTunnelServer_connect_Params creates a new list of TunnelServer_connect_Params.
|
// NewTunnelServer_obsoleteDeclarativeTunnelConnect_Params creates a new list of TunnelServer_obsoleteDeclarativeTunnelConnect_Params.
|
||||||
func NewTunnelServer_connect_Params_List(s *capnp.Segment, sz int32) (TunnelServer_connect_Params_List, error) {
|
func NewTunnelServer_obsoleteDeclarativeTunnelConnect_Params_List(s *capnp.Segment, sz int32) (TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List, error) {
|
||||||
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
|
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
|
||||||
return TunnelServer_connect_Params_List{l}, err
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List{l}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Params_List) At(i int) TunnelServer_connect_Params {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List) At(i int) TunnelServer_obsoleteDeclarativeTunnelConnect_Params {
|
||||||
return TunnelServer_connect_Params{s.List.Struct(i)}
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Params{s.List.Struct(i)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Params_List) Set(i int, v TunnelServer_connect_Params) error {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List) Set(i int, v TunnelServer_obsoleteDeclarativeTunnelConnect_Params) error {
|
||||||
return s.List.SetStruct(i, v.Struct)
|
return s.List.SetStruct(i, v.Struct)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Params_List) String() string {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List) String() string {
|
||||||
str, _ := text.MarshalList(0xa766b24d4fe5da35, s.List)
|
str, _ := text.MarshalList(0xa766b24d4fe5da35, s.List)
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
// TunnelServer_connect_Params_Promise is a wrapper for a TunnelServer_connect_Params promised by a client call.
|
// TunnelServer_obsoleteDeclarativeTunnelConnect_Params_Promise is a wrapper for a TunnelServer_obsoleteDeclarativeTunnelConnect_Params promised by a client call.
|
||||||
type TunnelServer_connect_Params_Promise struct{ *capnp.Pipeline }
|
type TunnelServer_obsoleteDeclarativeTunnelConnect_Params_Promise struct{ *capnp.Pipeline }
|
||||||
|
|
||||||
func (p TunnelServer_connect_Params_Promise) Struct() (TunnelServer_connect_Params, error) {
|
func (p TunnelServer_obsoleteDeclarativeTunnelConnect_Params_Promise) Struct() (TunnelServer_obsoleteDeclarativeTunnelConnect_Params, error) {
|
||||||
s, err := p.Pipeline.Struct()
|
s, err := p.Pipeline.Struct()
|
||||||
return TunnelServer_connect_Params{s}, err
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Params{s}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p TunnelServer_connect_Params_Promise) Parameters() CapnpConnectParameters_Promise {
|
func (p TunnelServer_obsoleteDeclarativeTunnelConnect_Params_Promise) Parameters() CapnpConnectParameters_Promise {
|
||||||
return CapnpConnectParameters_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
|
return CapnpConnectParameters_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunnelServer_connect_Results struct{ capnp.Struct }
|
type TunnelServer_obsoleteDeclarativeTunnelConnect_Results struct{ capnp.Struct }
|
||||||
|
|
||||||
// TunnelServer_connect_Results_TypeID is the unique identifier for the type TunnelServer_connect_Results.
|
// TunnelServer_obsoleteDeclarativeTunnelConnect_Results_TypeID is the unique identifier for the type TunnelServer_obsoleteDeclarativeTunnelConnect_Results.
|
||||||
const TunnelServer_connect_Results_TypeID = 0xfeac5c8f4899ef7c
|
const TunnelServer_obsoleteDeclarativeTunnelConnect_Results_TypeID = 0xfeac5c8f4899ef7c
|
||||||
|
|
||||||
func NewTunnelServer_connect_Results(s *capnp.Segment) (TunnelServer_connect_Results, error) {
|
func NewTunnelServer_obsoleteDeclarativeTunnelConnect_Results(s *capnp.Segment) (TunnelServer_obsoleteDeclarativeTunnelConnect_Results, error) {
|
||||||
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
|
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
|
||||||
return TunnelServer_connect_Results{st}, err
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results{st}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRootTunnelServer_connect_Results(s *capnp.Segment) (TunnelServer_connect_Results, error) {
|
func NewRootTunnelServer_obsoleteDeclarativeTunnelConnect_Results(s *capnp.Segment) (TunnelServer_obsoleteDeclarativeTunnelConnect_Results, error) {
|
||||||
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
|
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
|
||||||
return TunnelServer_connect_Results{st}, err
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results{st}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadRootTunnelServer_connect_Results(msg *capnp.Message) (TunnelServer_connect_Results, error) {
|
func ReadRootTunnelServer_obsoleteDeclarativeTunnelConnect_Results(msg *capnp.Message) (TunnelServer_obsoleteDeclarativeTunnelConnect_Results, error) {
|
||||||
root, err := msg.RootPtr()
|
root, err := msg.RootPtr()
|
||||||
return TunnelServer_connect_Results{root.Struct()}, err
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results{root.Struct()}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Results) String() string {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results) String() string {
|
||||||
str, _ := text.Marshal(0xfeac5c8f4899ef7c, s.Struct)
|
str, _ := text.Marshal(0xfeac5c8f4899ef7c, s.Struct)
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Results) Result() (ConnectResult, error) {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results) Result() (ConnectResult, error) {
|
||||||
p, err := s.Struct.Ptr(0)
|
p, err := s.Struct.Ptr(0)
|
||||||
return ConnectResult{Struct: p.Struct()}, err
|
return ConnectResult{Struct: p.Struct()}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Results) HasResult() bool {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results) HasResult() bool {
|
||||||
p, err := s.Struct.Ptr(0)
|
p, err := s.Struct.Ptr(0)
|
||||||
return p.IsValid() || err != nil
|
return p.IsValid() || err != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Results) SetResult(v ConnectResult) error {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results) SetResult(v ConnectResult) error {
|
||||||
return s.Struct.SetPtr(0, v.Struct.ToPtr())
|
return s.Struct.SetPtr(0, v.Struct.ToPtr())
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewResult sets the result field to a newly
|
// NewResult sets the result field to a newly
|
||||||
// allocated ConnectResult struct, preferring placement in s's segment.
|
// allocated ConnectResult struct, preferring placement in s's segment.
|
||||||
func (s TunnelServer_connect_Results) NewResult() (ConnectResult, error) {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results) NewResult() (ConnectResult, error) {
|
||||||
ss, err := NewConnectResult(s.Struct.Segment())
|
ss, err := NewConnectResult(s.Struct.Segment())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ConnectResult{}, err
|
return ConnectResult{}, err
|
||||||
|
@ -3686,37 +3688,37 @@ func (s TunnelServer_connect_Results) NewResult() (ConnectResult, error) {
|
||||||
return ss, err
|
return ss, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TunnelServer_connect_Results_List is a list of TunnelServer_connect_Results.
|
// TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List is a list of TunnelServer_obsoleteDeclarativeTunnelConnect_Results.
|
||||||
type TunnelServer_connect_Results_List struct{ capnp.List }
|
type TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List struct{ capnp.List }
|
||||||
|
|
||||||
// NewTunnelServer_connect_Results creates a new list of TunnelServer_connect_Results.
|
// NewTunnelServer_obsoleteDeclarativeTunnelConnect_Results creates a new list of TunnelServer_obsoleteDeclarativeTunnelConnect_Results.
|
||||||
func NewTunnelServer_connect_Results_List(s *capnp.Segment, sz int32) (TunnelServer_connect_Results_List, error) {
|
func NewTunnelServer_obsoleteDeclarativeTunnelConnect_Results_List(s *capnp.Segment, sz int32) (TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List, error) {
|
||||||
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
|
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
|
||||||
return TunnelServer_connect_Results_List{l}, err
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List{l}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Results_List) At(i int) TunnelServer_connect_Results {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List) At(i int) TunnelServer_obsoleteDeclarativeTunnelConnect_Results {
|
||||||
return TunnelServer_connect_Results{s.List.Struct(i)}
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results{s.List.Struct(i)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Results_List) Set(i int, v TunnelServer_connect_Results) error {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List) Set(i int, v TunnelServer_obsoleteDeclarativeTunnelConnect_Results) error {
|
||||||
return s.List.SetStruct(i, v.Struct)
|
return s.List.SetStruct(i, v.Struct)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TunnelServer_connect_Results_List) String() string {
|
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List) String() string {
|
||||||
str, _ := text.MarshalList(0xfeac5c8f4899ef7c, s.List)
|
str, _ := text.MarshalList(0xfeac5c8f4899ef7c, s.List)
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
// TunnelServer_connect_Results_Promise is a wrapper for a TunnelServer_connect_Results promised by a client call.
|
// TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise is a wrapper for a TunnelServer_obsoleteDeclarativeTunnelConnect_Results promised by a client call.
|
||||||
type TunnelServer_connect_Results_Promise struct{ *capnp.Pipeline }
|
type TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise struct{ *capnp.Pipeline }
|
||||||
|
|
||||||
func (p TunnelServer_connect_Results_Promise) Struct() (TunnelServer_connect_Results, error) {
|
func (p TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise) Struct() (TunnelServer_obsoleteDeclarativeTunnelConnect_Results, error) {
|
||||||
s, err := p.Pipeline.Struct()
|
s, err := p.Pipeline.Struct()
|
||||||
return TunnelServer_connect_Results{s}, err
|
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results{s}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p TunnelServer_connect_Results_Promise) Result() ConnectResult_Promise {
|
func (p TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise) Result() ConnectResult_Promise {
|
||||||
return ConnectResult_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
|
return ConnectResult_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4383,257 +4385,259 @@ func (p ClientService_useConfiguration_Results_Promise) Result() UseConfiguratio
|
||||||
return UseConfigurationResult_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
|
return UseConfigurationResult_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema_db8274f9144abc7e = "x\xda\xccz}\x90\x14uz\xff\xf3t\xcf\xd2\xbb\xb0" +
|
const schema_db8274f9144abc7e = "x\xda\xccZ{\x90\x15ev?\xa7\xfb\xde\xe9\x19`" +
|
||||||
"\xcbL\xdbc\xfdv)\xf67\xc7\x8bQ8!\"G" +
|
"\xb8\xb7\xed\xb1\x18X`\x041+\xac\x10u\xd6\xc4\x9d" +
|
||||||
"\xa2\x9b\xe4\xf6\x0d\xb8]\x0ea{g\x17u\xe5R\xf6" +
|
"$;O\xd8\x19\x16az\xee\x0c\xba#\xa6\xec\xb9\xf7" +
|
||||||
"\xce|w\xb6a\xa6{\xe8\xee\x01\x96\xe0\xf1R\x10e" +
|
"\x9b\x99\x86\xbe\xdd\x97~\x00CpA\x0a\xa2L`\x05" +
|
||||||
"\x03'x\x90\x02\x0f\xaf\x00\x8f\xf8\x12\xbd\x13\x0f+\xa7" +
|
"\x17R\xe0\xe2\x16\xe0\x12\x1fq\xb3\xe2be5jI" +
|
||||||
"\x11K\x93\xab\xa89\x13\xe5\x82)\xbd\x98\x8a\x11\xa8T" +
|
"\xb2\x1b$\xabQ6\x98\x92\x8dVT\xb0Rk\xad\xe5" +
|
||||||
"\xac\xb3<\xd1\x94eJ\xed\xd4\xf3\xed\xd7\x1d\x96\x05L" +
|
"\xa2\xa6,Sj\xa7N\xbf\xe7\xce0\x03k\xfe\xc8?" +
|
||||||
"R\x95\x7f`\xea\xe9\xe7\xfb\xf2\xbc}\x9e\x97\xef\xde\xf4" +
|
":u\xee\xe9\xefq\x1e\xbf\xf3;\xe7\xe3\xfa\x9d\x93\x9a" +
|
||||||
"\xe3\xc9m\xc2\x82\x9a\xeb\x93\x00\xeaS5\x93\\6\xf7" +
|
"\xb8\x1b\xd2'\xab\x01\xe4C\xe9\x0a\x97-\xf8\xd5\xc6C" +
|
||||||
"\x97\x9b\x8e\\\xf7\xd7\xdbA\x9d\x86\xe8~\xf7\xf9e\xe9" +
|
"\xd7\xfc\xd3V\x90g \xba\xdf}vi\xcd\xa7\xf6\xd6" +
|
||||||
"\xcf\x9c\xed\xff\x045\xa2\x04\xb0pD\xda\x84\xca\x1eI" +
|
"\xff\x804/\x00\xd4\x9f\xaa\xd8\x88\xd2\x9b\x15\x02\x80t" +
|
||||||
"\x02PvI\xff\x06\xe8\xd6\xfc\xd6\x9b\xef\x96\xdf\x95v" +
|
"\xb6\xe2\xbf\x00\xdd\xf4\x1f\xbc\xf6V\xe9-a\x1b\x883" +
|
||||||
"\x80<-\xce,\x10s\xa9v\x19*\xdbj\x89\xf9\x9e" +
|
"\x92\xca\x1c)?',E\xe9\xb4@\xca/\x0b\xeb\x01" +
|
||||||
"\xda\x0d\x80\xee\xef\x97^?\xf6;\x07~A\xccB\xc4" +
|
"\xdd?-\xber\xe4\x8f\xf6\xfd\x92\x94\xb9X\x19\xb0\xfe" +
|
||||||
"\x0c\xb8\xf0|\xed&T>\xe3\x9c\xffQ\xbb\x12\xd0\xfd" +
|
"\xa6\xca\x8d(uT\x92\xe6\xe2\xca\x15\x80\xeeG{k" +
|
||||||
"x\x7f\xe3\x9f\x1f\xfd\xfbWv\x82|=\x82\x7fvC" +
|
"\xff\xf6\xf0\xbf\xbe\xb8\x1d\xc4\xaf\"\x04{\xdfQ\xf9k" +
|
||||||
"\xdd\xaf\x10P\x99Q\xf7\x13@\xf7\x1fn\xda|\xee\xee" +
|
"\x04\x94\xd6V\xfe\x04\xd0\xfd\xb7\xeb7\x9d\xbf\xf3\xa3=" +
|
||||||
"\x8f\xf7\xdd7\xf6\xdc\x04\xf1\xbdT7\x8a\xca\xdbu\x12" +
|
"\xf7\x8e\xdc7Ezb\xd50J\xf3\xab\x04\xe0\xdd\x07" +
|
||||||
"\x88\xeeCw\xa5\xff\x16\x8f|\xba\x0f\xe4\x1bh\x1b\xa4" +
|
"o\xaf\xf9\x17<\xf4\xc9\x1e\x10\xaf\xa5e\x90~NW" +
|
||||||
"\xcf\xcf\xd4M\x16\x00\x95\xbf\xabk\x05t_\xbf\xf1\xf9" +
|
"M\xe2\x00\xa5YU\x8d\x80\xee+\xd7=\xfb\xcc\xee\x9f" +
|
||||||
"\xe7\xf6\xfe\xf4\xde\x1f\x80z=\"x\xeb?\xa8\xfbO" +
|
"\xde\xf3\x03\x90\xbf\x8a\x08\xfe\xf7\x7fV\xf5?\xb4\x8f\xec" +
|
||||||
":\x07'\x13\xc3G?\xfaz\xe2\xc9\xd7\xaf\xf9!g" +
|
")\\\xf8\xd1\xd7R?~\xe5\x8a\x1fz\x0a\xee\xd1\xd3" +
|
||||||
"p\x8f\x9f\xbe\xfd\xe9\xbd?\xfd\xda\xfb\xd0/H\x98\x00" +
|
"\xb7>\xb9\xfb\xa7W\xbd\x07=\x9c\x80)\x80\xfa\xa1*" +
|
||||||
"X8g\xb2E\xbc\x8b&\x93.\xf6\xbfujEi" +
|
"\x93t\xb7W\x91-\xf6\xbe\xfe\xdc\xf2\xe2\x9e\x07\x8e\xf8" +
|
||||||
"\xdf\x83\xc7\xbcK\xf3\xbd\xae\x9d\"\x08\x90pwt\x7f" +
|
"\x87\xf6\xd6b\x938\x0eR\xee\xb6\x8eO\x8a=\x0f\xe5" +
|
||||||
"Z\xea\x7f8\xfb\xb0/N\x0d}\xaa\x9br\x01\x01\x17" +
|
"\x1e\x0a\xae\x93\xa6\x9f\xbe3\xe9C\x04\xac/N\xaaC" +
|
||||||
"6O\xc9 \xa0\xbb\xe8W\xe7W\xde\xf6\xf4\xd0#>" +
|
"@\xf7\xa6_\xbf\xbb\xe2\x96'\xfb\x1f\x0e4\xbc\x93\xee" +
|
||||||
"\x07\xbf\xe9\xad\xf5O\xd3\xe6\xdd\xf5t\x91W6\xa4v" +
|
"\x98\xbc\x91Nzt2\x1d\xe4\xc5\xf5\xd9\x9d\xcd\x7f|" +
|
||||||
"\xb7\xff\xee\xfd\x8fT\x9b\x85\xefU\xaa\x1fEeg=" +
|
"\xdf\xc3\xe5n\xf1\xd6:5y\x18\xa5\xb7'\xd3\x9fo" +
|
||||||
"\xfd\xdcV\x7f;\xed7z\xeb\xa9U\x1f\xff\xb1\xfd8" +
|
"N\xbe\x95\xd6\x1b\xfe\xc6s+?\xfaK\xeb1\x90\x17" +
|
||||||
"\xa8\xf30\xe1\xfe|\xd7\xd9\xf5s\x1e\x1bz\x99\xdf[" +
|
"b\xca\xfd\xf9\x8es\xeb\xe6?\xda\x7f\xd2;7\x0fP" +
|
||||||
"\x04X\xf8Y\xc3/i\xeb\x86\xa9\xa4\xcb\x86\xbf\x9c\xbb" +
|
"\xdfS\xfd+:\xb7ZM\xb6\xac\xfe\x87\x05\xcb\xef;" +
|
||||||
"\xe2\xfes\xcbO\xd0\xd61\xbbx\x97xrj\x0b*" +
|
"\xbf\xec\x18-\x9d\xf0\x8b\x7f\x88\xaa\xa9\x0d(M\x9fJ" +
|
||||||
"\xa7\xa6\x92i\x9e\xe5\xdco\xdc\xb8\xea\x85\x17\x9e*\x9c" +
|
"\xae\xb9r*i\xbfz\xdd\xca\xe7\x9f\x7fb\xe0X\xf9" +
|
||||||
"\xa8\xbe\x087\xf9\x9d\xc9e\xa8\x94\x92\xc4\xad'\x89\xfb" +
|
"A<\x97?5u)J\xa7<\xed_x\xdaWv" +
|
||||||
"\xdan|\xe7\xc5\x05\x89\xbf\x88\x1b\xb29\xf5>\x1d\xbe" +
|
"\xe0\x1b/\xdc\x90\xfa\xfb\xa4#\x872\xef\xd1\xe6\xbb2" +
|
||||||
" E\x0cw}\xfe\xcc_-\xf9\xf0\xcc\xb3q\x13\x9d" +
|
"\xa4p\xfbgO\xfd\xe3\xe2\x0f\xce<\x9dt\xd1\xc2," +
|
||||||
"N\x09d\xa2\xf3)\x12|`\x14K\xef\xb4\xb4\xbd\x00" +
|
"G\x17o\xce\xd2\xc5{\x87\xb1\xf8FC\xd3\xf3 _" +
|
||||||
"\xea\x0d\x88\xee\x9a\x03\x9b\x9d\xae\x83{\\\xe8G\x09\x05" +
|
"\x8b\xe8\xae\xde\xb7\xc9n\xdf\xbf\xcb\x85\x1e\x14\x90\x03\xa8" +
|
||||||
"\xf2\x0ay\x13m\xd6$\x93\x835\x7f\xd0\xd1`|\xb8" +
|
"W\xb3\x1bi1'K\x016\xeb\xfd\x96j\xfd\x83\xad" +
|
||||||
"\xfd\xc5*o\xe4\xa7V\xe4e\xa8\xec\x92\xe9j;\xe5" +
|
"/\x94E\xa3\xb7\xeb\xe9\xecR\x94\xde\xcd\xd2\xd1\xde\xce" +
|
||||||
"\x9f\x00~\xfa\xf8\xbd{\xbb\xcf.~Y\x9d\x86\x89j" +
|
"\xfe\x04\xf0\x93\xc7\xee\xd9\xddq\xae\xed\xa4<\x03S\xe5" +
|
||||||
"\xa1g\\\xb3\x09\x95E\xd7\xd0\xcf\x05\xd7p\xfb\x84\x1a" +
|
"\x97\xbeK\xdc\x88\xd2\x1e\x91\xfe\xdc%z\xfe\x89,X" +
|
||||||
"\xacb\xe7Rk\xca\x1aT*\x0a\xfd\\\xa7p\xf6e" +
|
"\xa6\xee\x07\xfa\x15\xabQ:}\x05\xfd\xf9\xf2\x15\x9e\xfa" +
|
||||||
"w}\xff\x81\x9a\xf3\xdf\x7f\xb9Z\xa5\xe4\xe2\x0b\xefI" +
|
"\xd2\xdb\xbf\x7f\x7f\xfa\xdd\xef\x9f,7)\x85x\xfdY" +
|
||||||
"[\xa8\xecK\xd3\xcf=\xe9\xff'\x02\xba\xd3\x9e\xfa\xbd" +
|
"\xc9D\xe9}\x89\xfe\xfc\x8d4\x8d\x07tg<\xf1'" +
|
||||||
"\x1fw\xe4\xdf\xfe\xc58Q\xa444^P\x9a\x1a\xe9" +
|
"\x7f\xd7R8\xfb\xcb1\xb2HR\xa7}(9\xd3\xe8" +
|
||||||
"\xd7\xb5\x8d$\xe3\xd9y'\xfe\xe8\xdf\xf7\x9c>\x13\xf7" +
|
"\xaf\xb5\xd3\xe8\x8e\xe7\x16\x1e\xfb\x8b\xdf\xec:}&\x19" +
|
||||||
"\x94u\x8d\xdcew6\x92\xc2\xee\xfd\xfa\xc8\xa6\x15\xd7" +
|
")/O\xf3B\xf6\xedid\xb0{\xbe6\xb4q\xf9" +
|
||||||
"\x8d\xbeYm \xcey\xbcq\x14\x95S|\xbbg\xf9" +
|
"5\xc3\xaf\x95;\xc8\xd3\xc4\xdaa\x94\xa6\xd7z\xee\xac" +
|
||||||
"v\xc2y\xadi\xeb?~\xf3\x9d\x98\xd3\xceiz\x0f" +
|
"\xa5\xe5\xb8w\x95\xe9[\xfe\xfd\x9bo$\x82v{\xed" +
|
||||||
"!\xe1\xaeXu\xd7\x9a\xba{\xce\x9e\x8d\x1f\xd4\xdc\xe4" +
|
";\x08)w\xf9\xca\xdbWW\xddu\xee\\r\xa3\xa1" +
|
||||||
"\x99\xae\x89\x0e:)?\xa0<\x7f\xf4\xcf\xce\xd1AR" +
|
"Z\xdfu\xb5\xb4\xd1q\xf1~\xe9\xd9\xc3\x7fs\x9e6" +
|
||||||
"\xb5\xba\xd5\xa6\x01TX\x13WO\xd3#\x02\xc4\x82g" +
|
"\x12\xca\xcd\xfd\xe3\xda^\x94N\xd4z\xe6\xa9}\x98\x83" +
|
||||||
"<\xc7\xf9\xce\xf4\x16TJ\xd3\xb9\xe3L\xa7{-\xba" +
|
"D\xf2\x8c\x158O\x7f\xa5\x01\xa5S_\xf1\x02\xe7+" +
|
||||||
"\xbb\x9d\xad\xbe\xe5\x8e\xf7A\x9e&\x8e\xc1\x8a\xc7\x88\xf3" +
|
"t\xae\x9b\xeelf\xabn\xbe\xed=\x10g\xf0#\xb0" +
|
||||||
"\xd9\xe9<\x94\xa7\xdf\x8b\xca\xa9f\x09\xc0\xfd^a\xe0" +
|
"\"=\xb3\x01\xa5+gz\x99>\xf3\x1e\x94\xe6\xcf\x12" +
|
||||||
"\xd5\x8f:\x8f\xfe\xa6zs.\xd0\xf1\xe6\x16T\x9e!" +
|
"\x00\xdc\xef\x0d\xf4\x9e\xba\xd0z\xf8w\xe5\x8b\xfb\x880" +
|
||||||
"\xbe\x85'\x9a\xb9}\x16.\xf8\x93\x0f\x0e<\xdc\xf9\xd1" +
|
"\xab\x01\xa59\xa4W?k\x96\xe7\x9f\xfa\x1b\xfe\xea\xfd" +
|
||||||
"E\xbb\x7f\xf1\xff;Pi\xc8\xd0=\xea2\xdfRn" +
|
"}\x0f\xb5^\x18\xb5\xba:\xbb\x05\xa5\xa1\xd9t\x0eg" +
|
||||||
"\xcd\xf0\xcd\xbf\xbbx\xe5\xad3_\xba\x10\xd7\xc4\x8c\xcc" +
|
"\xf6\xb7\xa4\xa3\xb3\xbd\xc5\xbf\xdb\xb6\xe2\x1bsO|\x98" +
|
||||||
"\x05\x1e\xf9\x19\xd2\xc4\xd0-\xbf\xfe\xd6u\xdf\xfb\x9b\x0b" +
|
"\xb4\xc4\xae\xd9\x94\xbe\xd2\xe1\xd9d\x89\xfe\x9b\x7f\xfb\xad" +
|
||||||
"U\xf6\xe3\x8c\xfd\x99\xb9\xa80\xbe\xa3F\xcc\x1f.\xfd" +
|
"k\xbe\xf7\xcf\x1f\x96\xf9\xcfS<1{\x01J\xa7\xbd" +
|
||||||
"\xe1\x99i\xc9i\x9fT]t\x12\xf1\xee\xcc\xacA\xe5" +
|
"\x15_&\xe5\x0f\x96\xfc\xf0\xcc\x8c\xcc\x8c\x8f\xcb\x0eJ" +
|
||||||
"\x10\xf1.<\x90y\x99.z\xc7{\x0fnh\xfd\xc1" +
|
"\x98Z\x7fa\xf6j\x94\xd2u\x9e\xa3\xeaN\xd2Ao" +
|
||||||
"'\x9f\x92\\b\x15\xd0\xed\x9a1\x80\xcaC3h\xe7" +
|
"{\xe7\x81\xf5\x8d?\xf8\xf8\x13\xba\x17_\x06t\xff}" +
|
||||||
"C3(\x96\x96?\xf1\xf67\x87\x0f\xbc\xf2\xd9\xb8\xd0" +
|
"U/JUsh\xe5\xf4\x1c\xca\xa5e\x8f\x9f\xfd\xe6" +
|
||||||
"\xbdd\xe6vT\xee\x9cI\xdc\xfd3\x09\xae\xfeT:" +
|
"\xe0\xbe\x17?\x1d\x13\xba\x8f\xcd\xd9\x8a\xd2/<\xed\x13" +
|
||||||
"|v\xeb\xbf\xfc\xe1\xe7q\xa9\xfe`\xd6{$\x95:" +
|
"s\x08\xae\xfeZ8xn\xcb\x7f\xfe\xf9g\xc9[=" +
|
||||||
"\x8b\xa4\xda\xfc\xe1\xa1\xae\xfbW?\xf1\xe5\x18O\x9b\xf5" +
|
":\xf7\x1d\xba\xd5ss\xe9V\x9b>8\xd0~\xdf\xaa" +
|
||||||
"\x1c1l\xe3\x0ca0\x8e\xe7iGgu\xa0rb" +
|
"\xc7\xbfH*\xbc9w+\xa5\xe6\xa7\x9eB\x94\x8cc" +
|
||||||
"\x16\x9d\xf7\xe4\xacV\x98\xe7:\x15\xc3`E\xab\x9c\xc8" +
|
"E\xda\xf4\xab[P\x9a\x7f5\xedw\xcd\xd5\xa4m;" +
|
||||||
"\xfdv\xf037?\xa7\x95\x8drK{\xc5\x19f\x86" +
|
"\xba\xce4\xb3\x94\xca\xffa\xf8g~Q^)\xe9\xa5" +
|
||||||
"\xa3\xe74\x87\xf5\xb2V\xbbl\x1a6\xebATSb" +
|
"\x86f\xc7\x1ed\xba\xad\xe6\x15\x9bu\xb1F\xabd\xe8" +
|
||||||
"\x02 \x81\x00\xb2\xb6\x06@\xbd[D\xb5(\xa0\x8c\x98" +
|
"\x16\xebD\x94\xb3|\x0a \x85\x00\xa2\xb2\x1a@\xbe\x93" +
|
||||||
"&\xb8\x96u\"\x0e\x8b\xa8:\x02\xca\x82\x90&D\x90" +
|
"GY\xe3PD\xac!\xb8\x16U\x12\x0e\xf2(\xdb\x1c" +
|
||||||
"\xd7\xcd\x04P\x8b\"\xaa\x1b\x05D1Mx'W\x1e" +
|
"\x8a\x1cWC\x88 \xae\x9d\x0b k<\xca\x1b8D" +
|
||||||
"\x00P7\x8a\xa8\xee\x10\xd0-3\xab\xa4\x19\xcc\x80\xa4" +
|
"\xbe\x86\xf0Nt\xee\x07\x907\xf0(o\xe3\xd0-1" +
|
||||||
"\xb3\xc4\xb2\xb0\x1e\x04\xac\x07t-\xe6X#\xda`\x11" +
|
"\xb3\xa8\xe8L\x87\x8c\xbd\xd84q\x0ap8\x05\xd05" +
|
||||||
"\x92,F\x96\xd6lp\xb0\x01\x04l\x00t\x87\xcd\x8a" +
|
"\x99m\x0e)}\x1adXB,\xac^oc5p" +
|
||||||
"e\xf7\x1b\x0e\xea\xc5^6d1\x1b\x87q\x12\x088" +
|
"X\x0d\xe8\x0e\x1a\x8ei\xf5\xe86\xaaZ\x17\xeb7\x99" +
|
||||||
"\x09p\"\xf1:M\xc3`9'[\xc9\xe5\x98m\x03" +
|
"\x85\x83X\x01\x1cV\x8c\x7f\xbdVC\xd7Y\xde\xce9" +
|
||||||
"\x90d\xb5\xa1ds\x1e\x04Po\x14Q\xbd%&\xd9" +
|
"\xf9<\xb3,\x00\xbaYet\xb3\xf9\x0f\x00\xc8\xd7\xf1" +
|
||||||
"\"\x92\xec\x1b\"\xaam\x02\xba6\xb3\xd63k\xb9\x89" +
|
"(\xdf\x9c\xb8\xd9Mt\xb3\xaf\xf3(7q\xe8Z\xcc" +
|
||||||
"9\xcd\xd1Mc\x85&\x96Xx\xed\\Qg\x86\xd3" +
|
"\\\xc7\xcce\x06\xe6\x15[5\xf4\xe5\x0a_d\xd1\xb1" +
|
||||||
"iB\xd2\x18\xd2\x0b\x98\x8aB\x01\x10S\x13_l\xc9" +
|
"\xf3\x9a\xcat\xbb\xd5\x80\x8c\xde\xaf\x0e`6N\x05@" +
|
||||||
"F\xddvt\xa3\xd0\xc7\xe9\xad=fQ\xcf\x8d\xd0\xed" +
|
"\xcc\x8e\x7f\xb0\xc5\x1bT\xcbV\xf5\x81nO\xde\xd8i" +
|
||||||
"\xea\xb9&\x9b[h\x0f\xf9\xda\x01\x00\x14d\xb9\x03\xa0" +
|
"hj~\x88N7\xc5\xb3\xe4\xac\x06ZC\xbc\xb2\x17" +
|
||||||
"U/\x18\xa6\xc5\xdc\xbcn\xe7H(\x10s\xce\x96A" +
|
"\x009Ql\x01hT\x07t\xc3dnA\xb5\xf2t" +
|
||||||
"\xad\xa8\x199\x16\x1e4\xe9\xe2\x83\xbc\x03\xb2\\\x8e\xf9" +
|
")\xe0\xf3\xf6\xe6>ES\xf4<\x8b6\xaa\x18\xbd\x91" +
|
||||||
"Z\xcc\xda\xb3{4K\x13K\xb6Z\x1f\xeac\xc9\x00" +
|
"\xbfA\xce\xbb\xc7\"%\xe1\xedy\x9d\x8a\xa9\xf0EK" +
|
||||||
"\x80\xbaXD\xb5'\xa6\x8f\xdb\x96\x01\xa8\xcbET\xef" +
|
"\x9e\x12\xd9cq/\x80\xdc\xc6\xa3\xdc\x99\xb0\xc7-K" +
|
||||||
"\x88Y\xba\xbf\x03@\xed\x11Q]-\xa0kZzA" +
|
"\x01\xe4e<\xca\xb7%<\xdd\xd3\x02 w\xf2(\xaf" +
|
||||||
"7:\x19\x88V\xdc`\xb6ch%\x06\x00\x81\xc2\xb6" +
|
"\xe2\xd05Lu@\xd5[\x19\xf0f\xd2a\x96\xad+" +
|
||||||
"\x98eR\xa2\x8d\xa9\x08\xa5\xab4Us\xb1\x00]\xac" +
|
"E\x06\x00\xa1\xc16\x1b%2\xa2\x85\xd9\x18\xa5\xcb," +
|
||||||
"X4o7\xadb~\xa5w\x8eI\xda\xe6\xa6\x0c\x97" +
|
"\x95\x1e}\x81v\xa6i\xc6\xad\x86\xa9\x15V\xf8\xfb\x18" +
|
||||||
"I\xe3X\x9e\x1b\x87\xe4\xd6sl~\xc5f\xde\xba\x8a" +
|
"dm\xcf\x95\xd1g\xc2\x18\x9e\xf7\x9cC\xf7V\xf3l" +
|
||||||
"\xc5\x0d9\xbb\x97\xd9\x95\xa2c\x03\xa8\x89P\xfc\x86\x16" +
|
"\x91c1\xff;\xc7\xf4\x1c9\xaf\x8bY\x8ef[\x00" +
|
||||||
"\x00\xb5VD5-`\xab\xc5\x190\x15\x81z\xd5U" +
|
"r*\xba~u\x03\x80\\\xc9\xa3\\\xc3a\xa3\xe9)" +
|
||||||
"/\xa7\xeb\x8aa\xb1\x82n;\xcc\xf2\xc8\xb3[I\xe1" +
|
"`6\x06\xf5\xb2\xa3NdkG7\xd9\x80j\xd9\xcc" +
|
||||||
"%;~ \xf9_JDu\xba\x80n\xc1\xd2r\xac" +
|
"\xf4\xc5\xf3\x1a\xc9\xe0E+\xb9!\xc5_\x96Gy&" +
|
||||||
"\x87Y\xa8\x9b\xf9\x15\x9aafE\x96\xc3\x1a\x10\xb0f" +
|
"\x87\xee\x80\xa9\xe4Y'3Q5\x0a\xcb\x15\xdd\xc8\xf1" +
|
||||||
"bOZ\xaa\xe9E\x96\xf7\xa4\x9b\x9f\xcb\xf0\xff)z" +
|
",\x8fi\xe00=~$-QT\x8d\x15\xfc\xdb-" +
|
||||||
"\xeb]\xd7\x0b\xdf\x81(|\x1b\xf0K\xd7\x8f\xdfMQ" +
|
"\xca\xd7y\xff\xa7\xec\x9d\xe2\xba~\xfa\xf6\xc6\xe9[\x8d" +
|
||||||
"\xfc6\x08_\xb8\x17\x07p\x83\xf8\xb9\xeb\x870E\x84" +
|
"_\xb8A\xfen\x8c\xf3\xb7\x9a\xfb\xdc\x1d\x9d\xc0\xd5\xfc" +
|
||||||
"#\xa2\xba\x95\"\xa2R&\x9d\xda \x9a\x16\xa6\"\x94" +
|
"gn\x90\xc2\x94\x116\x8f\xf2\x16\xca\x08\xa7D6\xb5" +
|
||||||
"\xf4\xb5\xc3\xf2\x05\xd2\xb4\x01\xad,G\x8a\xc6T\x90\xed" +
|
"\x807L\xcc\xc6(\x19X\x87\x15\x06\xc8\xd2:4\xb2" +
|
||||||
"=\x06)o\x0ec**e\xfce\x16[\xcf,\x9b" +
|
"<\x19\x1a\xb3a\xb5\xf7\x15\x84\x821\x88\xd9\x98\xca\x04" +
|
||||||
"\xf5@\xd227\x8e`*\xca\xfaUZ\x9fz\xb5Z" +
|
"\x9f\x99l\x1d3-\xd6\x09\x19\xd3\xd80\x84\xd9\xb8\xea" +
|
||||||
"\x0f\x0c\x1d\xae\x9ax\xbd\xc5r\x1ed\xf8\xcb{2\x9e" +
|
"\x97Y}\xea\xe5Z=tt\xf4\xd5\xf8\xdf\x9b,\xef" +
|
||||||
"\xd1\xd2\xa1\xd1\xee\x99\x19\x01Z\x18$\xdb\x06\x01\xd4\xad" +
|
"CF\xf0yg\x9d\xef\xb4\x9a\xc8iw\xcd\x8d\x01-" +
|
||||||
"\"\xaa\xbbcA\xb2\x8b4\x7f\x9f\x88\xea~\x01e\xd1" +
|
"J\x92\xbb\xfb\x00\xe4-<\xca;\x13I\xb2\x83,\x7f" +
|
||||||
"\xc7\xc3}\x14N{ET\x0f\x0b('\x12i*f" +
|
"/\x8f\xf2^\x0eE>\xc0\xc3=\x94N\xbby\x94\x0f" +
|
||||||
"\xe5C\x14N\xfbET\x8f\x08ca\x8f\xadg\x86\xb3" +
|
"r(\xa6R5Df\xc5\x03\x94N{y\x94\x0fq" +
|
||||||
"X/\x80\xc4\xec\x88JW\\\xac\x17\x18\x88\xf6\xffB" +
|
"#a\x8f\xadc\xba\xdd\xa6\x0e\x80\xc0\xacXJGl" +
|
||||||
"\xc0\x8d\xd1\x87\xaf\x8dP\x0f1\xe7%\xe9\xeaET\x1b" +
|
"S\x07\x18\xf0\xd6\x97M\xb8\xca\x09\xeca\xf4Y\x86\xc6" +
|
||||||
"\x09\xd8\xe9+s\x08\x02\xe8\xb4\xb0d\xbe\xfci\x9d\xf4" +
|
"l\xd6\xc6\xf2\x9aB\xb9\xb3\x8e\xf9\xbf\x07\xc8:VT" +
|
||||||
"\xaf\x0f\xd3=\xfe.\x96\x8f\xd4\x8d\xe1a\x87\xe8\xb0\x83" +
|
"\xd3\xb5\xa7\xf0(\xd7\x12\xe2\xd3\xaf\xcc&l\xa0cD" +
|
||||||
"\"\xaa?\x8a)\xfd\xa8\x05\xa0\x1e\x11Q}B@\xf4" +
|
"\\z\xe2\xbco\xa5\xff\x06\xbbt\x06\xab\x98\x01\x84\xd7" +
|
||||||
"u\xfe\xd81\x00\xf5\x09\x11\xd5\x9f\x91\xce\x05O\xe7\xcf" +
|
"F\x9b\x1d\xa0\xcd\xf6\xf3(\xff(\xe1\x8d\xc3&uT" +
|
||||||
"\xcc\xa5\x0eKD\xf55\xd2\xb9\xe8\xe9\xfcU\x0a\xbe\xd7" +
|
"<\xca\x8fs\x88\x813\x1e=\x02 ?\xce\xa3\xfc3" +
|
||||||
"DT\xdf\x12P\xaeI\xa4\xb1\x06@~\x93\xecxF" +
|
"r\x06\xe7;\xe3\xa9\x05\x00\xf2\x13<\xca/\x913x" +
|
||||||
"D\xf5\xddK\xe1Z\xaehV\xf2CE\x0d2\x16\xcb" +
|
"\xdf\x19\xa7(+_\xe2Q~\x9dC1\x9d\xaa\xc14" +
|
||||||
"w/\x0e\xe9F\xa5\xd4c\xb1\xf5:\x9a\x15\xbb\xddq" +
|
"\x80\xf8\x1a9\xf8\x0c\x8f\xf2[\x17\x03\xbc\xbcf8\x85" +
|
||||||
"XI*;v\x90\xa2\x92\x8eV\xb0q*`\x8f\x88" +
|
"~M\x81:\x93\x15:\xda\"\xb9\xee\x14;M\xb6N" +
|
||||||
"\x98\x8a\x8aN@\"\x86{\xa2\xc5\xf2\xab\x98e\xeb\xa2" +
|
"E\xc3\xb1\x9am\x9b\x15\x85\x92m\x85\xb5+c+\x03" +
|
||||||
"i\x84YF7\x1cf8\xcb5\x90\x06Y1\xa4N" +
|
"\x16N\x05\xec\xe4\x11\xb31\x1b\x05$a\xb4&\x9a\xac" +
|
||||||
"\x80B\xbd~,Q$\xf9\xb0`F\xc8\x89\x05\x02\xfc" +
|
"\xb0\x92\x99\x96\xca\x1bzT~T\xddf\xba\xbdL\x01" +
|
||||||
"\xe9\xae\xeb+q\x09\xe9\xa6MDu\xb9\x80\xcd\xf8%" +
|
"\xa1\x8fi\x91t\x1cx\xea\x0a\x92\x8cR,\xc0\x0b#" +
|
||||||
"\x91I\x8f\xdd\xbd\x00j\x97\x88j\x9f\x80\xcd\xc2\x17D" +
|
"\x86T\x1c\xa0J0\xd3u\x03#.&\xdb4\xf1(" +
|
||||||
"&M\xaa\x03\x11\xee'\x87\x1d\xa7\x8c\xa9\xa8\x18\xf5\x8d" +
|
"/\xe3p\x16~Ab\xb2cG\x17\x80\xdc\xce\xa3\xdc" +
|
||||||
"\xbd\x81\x0d\xdafn-\x03$\xf8\x0c+#\xff\xeb\xb0" +
|
"\xcd\xe1,\xees\x12\x93%\xe5\xde\xb8 d\x06m\xbb" +
|
||||||
"\x0f\xe7 \x16\xf3\x98\x8a\xba\xc9*O\x11/\x95\xcb[" +
|
"\x84\xd9\x98\xa5\x06\xce^\xcf\xfa,#\xbf\x86\x01\x12\xae" +
|
||||||
"\xa9r0-\x9e*\xa3\xc4us$D\xe0\x1d\xdd\x03" +
|
"F\x94)\xf8u0\xc0y\xe0\xb5\x02f\xe36\xb3," +
|
||||||
"\x91\x04\xb2\xd0\xe6\x89\xa5\x0eF\xf7\xcf\xe4\xb4\x8a\xcd\xc6" +
|
"R\xf8\x8b\x15\xf9F\xa2\x14\x86\xe9\xd5\xd0\xb8\xa2\xdd\x18" +
|
||||||
"\x16!\xedC\x0e\x88\xcc\x0aq\xd7\x1e6+\xc5|/" +
|
"_\"\x8c\x8e\x8e\xde\xf8\x06\"\xd7\xe4_K\xee\x8b\xcf" +
|
||||||
"\x03\xc9\xb1F\x10A@\x9c\x18\x8d\x17\x9b]1\xc5{" +
|
"_\x97W\x1c\x8b\x8dd'\xcd\xfd6\xf0\xcc\x8c\x00\xd9" +
|
||||||
"n<~\x82\x0d\xf3\xeb@<\xbf\xfa\xea\xef'\xf5\xf7" +
|
"\x1a4\x1c\xad\xd0\xc5@\xb0\xcd!D\xe0\x10\xc7\x87\xe9" +
|
||||||
"\x89\xa8\x96\x05t\x8b\x84gF\x97\xc9\xc3=\xb8\xaeG" +
|
"6\xa3=ax?\x8c\xc7\xae\xbcQ\xe1\xedM\x16\xde" +
|
||||||
"\xec1\xb9sJ \xa0\x04\xe8V\xca\xb6c1\xad\x04" +
|
"\xc0\xfc=d\xfen\x1e\xe5\x12\x87\xaeF@\xa7\xb7\x1b" +
|
||||||
"\x18z\x1b\xf1O\xbd\x8a\xb4U\x05\x9f=Z\x92\xc7\xfd" +
|
"\x1e\x0e\x84\xc7\xf5\x85\x9d\x86\x17\x9c\x02p(\x00\xbaN" +
|
||||||
"\xff\xa5\"\xe1\xea\xb3\xbd\x97y\xc7\xe4\xfac\xb1\xd4\x9b" +
|
"\xc9\xb2M\xa6\x14\x01\xa3h#\xfd\xa9\x97Q\xcf\xcap" +
|
||||||
"\xf3W#_\xdei\x1a\xd2U\xd7s>\x82y\xd9f" +
|
"\xb5S\xc9xy\xff\xff\x89=\\>\x0d\xf0\xc1k\x04" +
|
||||||
"\xbe_=P\xa9\x19\xa4\xe19\x946f\x8b\xa8\xde\x14" +
|
"\x098\x92\xa8\xc9\xf9\xe0k\xf4>o5t\xe1\xb2\x89" +
|
||||||
"O\xc3\xf3HE7\x88\xa8~C@\x89Y\x94Q\xc3" +
|
"^\x80`~\x19Z\x14\xd0\x0a\xe2\xa0a}\x9eO\xf5" +
|
||||||
"\x99\x80w\xe8\x16\xdb\xab]1\x15M|.\x7f\x9dX" +
|
"d\x1e\x8f\xf2\xf5\xc9\xfa\xbc\x90Lt-\x8f\xf2\xd79" +
|
||||||
"Y\xaf\x9b\xc6En83\x0a\x97\xd0\x84\xdd7\xc7\xec" +
|
"\x14\x98I\xa56\x1a\x16\xf8\x9bn\xb6|R\x8b\xd9x" +
|
||||||
"\x1a\x98\xf0\xb6\xc1\xc8\xae\xd2Z6\x12X)\xc3J\x9a" +
|
"\x144\xf1q\x12|_5\xf4Qa87N\x97\xc8" +
|
||||||
"\x1e\xa1\x91o\xdcv\x90\xbe\x1d\xf1LX\xfe\xfae\x82" +
|
"\x85\x1d7&\xfc\x1a\xba\xf0\x96\xbe\xd8\xaf\xc2\x1a6\x14" +
|
||||||
"W$\xb4z\xd6\xa2K\xc6\xf2\xech,\xa5\x06\x97\xdc" +
|
"z\xa9\x8e\x15\x155F\xa3\xc0\xb9\xcd |;\xd6\x19" +
|
||||||
"E\xdd\xc4n\x11\xd5\x83\xb1K\x1e\xe8\x88\xa5\xd4 \xcf" +
|
"\x97\x17\x07\xfc\xc1g\x0f\x8d\xbe\xb7\xe8\x90\x89\x02<\x9c" +
|
||||||
"\x1e\"\x03\x1f\x16Q}T@\xf4\xd3\xecq\x82\xfcG" +
|
"\xa8\xb5\xe1!wP\x9b\xb1\x93Gy\x7f\xe2\x90\xfbZ" +
|
||||||
"ETO\x0a\x1c\xb0\xbb\xda;M\x03\xfdK\xd8\x00a" +
|
"\x12\xb56,\xc0\x07\xc8\xc1\x07y\x94\x1f\xe1\x10\x83\xfa" +
|
||||||
"G1\xcc4\xcb\x19d\x1a:\xdd\x86\xc3\xac\xf5\x1a\x16" +
|
"{\x94 \xff\x11\x1e\xe5\xe3\x9c\x07\xd8\xed\xcd\xad\x86\x8e" +
|
||||||
"\x03H\xd8\xe2\xe8%fV\x9c\x10\"J\xdaF^\x82" +
|
"\xc1!,\x80\xa8\xd5\x18d\x8ai\xf71\x05\xed\x0e\xdd" +
|
||||||
"a\xbe\xcb[%i\x8e\x8du `\x1dE\xa4\xcd\xac" +
|
"f\xe6:\x05\xb5\x10\x126\xdbj\x91\x19\x8e\x1dAD" +
|
||||||
"N\x8b\xe5\x91\xac\xa1\x15{4\xd1\x19\xbe\x12\x05\x8d\x05" +
|
"Q\xd9\xe0q3,\xb4\xfb_\x09\x8ama\x15pX" +
|
||||||
"\xf1\xe48\xea\xa1\x02n\xb3\x88\xea}\x04%\x18\x9b;" +
|
"E\x19i1\xb3\xd5d\x05$o(Z\xa7\xc2\xdb\x83" +
|
||||||
"\xc9;\xd7\x80\xc0\x91\x84d^\xd7\x11\x95t<!\xd6" +
|
"\x97b\xa0\x91 \x9e\x19\xc3<\xc4\xec6\xf1(\xdfK" +
|
||||||
"T5e<!N\xa2\x1a\x86\xb4\xb3CDu\xaf\x10" +
|
"P\x82\x89\x81\x94\xb8}5p\x1e\x92\xd0\x9d\xd7\xb6\xc4" +
|
||||||
"\\\xad\xcb\x84V/B\xabM\xed7=[\x085u" +
|
"\\\xcf+\x88\xe9\xb2n\xcd+\x88\x15Dn\xc8:\xdb" +
|
||||||
"\x16\xc9\xeb\xd7\x0b:\x9aF\x1fW\x14F\x9a\xca\x99\xa5" +
|
"x\x94ws\xe1\xd1\xda\x0dh\xf43\xb4\xdc\xd5A7" +
|
||||||
"\xb2E\xae\xac\x9b\x86Z\xd1\x8a\xba\xe8\x8c\x84\x0b'\xd4" +
|
"\xb4\x99PSe\xf1}\x03Z\xa5\xa2\xa1w{\x86\xc2" +
|
||||||
"\x05A\x92\x17\xca+\xcb\x19n,R\xc6-\x812\x94" +
|
"\xd8Ry\xa3X2)\x94UC\x97\x1dESy{" +
|
||||||
"\x11\\\x06\x90\xdd\x88\"fw`\xe4.\xca6\xec\x00" +
|
"(\xfap\\[\x10$\xf9\xa9\xbc\xa2T\xe79\x8b\x8c" +
|
||||||
"\xc8n&\xfa}\x18y\x8c\xb2\x13\xa7\x01d\xb7\x12}" +
|
"qsh\x0ci\x08\x97\x02\xe46 \x8f\xb9m\x18\x87" +
|
||||||
"7\x86\xbd\xaa\xb2\x0b\x1f\x07\xc8\xee&\xf2A\x8cJ\x05" +
|
"\x8bt7\xb6\x00\xe46\x91\xfc^\x8c#F\xda\x8e3" +
|
||||||
"\xe5\x00\xdf~?\xd1\x8f`T-(\x0f\xe1\\\x80\xec" +
|
"\x00r[H\xbe\x13\xa3&V\xda\x81\x8f\x01\xe4v\x92" +
|
||||||
"A\xa2\x9f$\xfa$\x81kR9\x81k\x00\xb2O\x11" +
|
"x?\xc6TA\xda\xe7-\xbf\x97\xe4\x870f\x0b\xd2" +
|
||||||
"\xfdy\xa2K5i\xe4s\x1f\xb4\x00\xb2?#\xfa\xcf" +
|
"\x83\xb8\x00 \xb7\x9f\xe4\xc7I^\xc1y\x96\x94\x8e\xe1" +
|
||||||
"\x89^\xdb\x98\xc6Z\x00\xe5%N\x7f\x91\xe8\xaf\x11\xbd" +
|
"j\x80\xdc\x13$\x7f\x96\xe4B\xba\x86:v\xe9i4" +
|
||||||
"\xae)\x8du\x00\xca\xab\xb8\x1d \xfb\x0a\xd1\xcf\x10}" +
|
"\x01r?#\xf9\xcfI^Y[\x83\x95\x00\xd2\x09O" +
|
||||||
"2\xa6q2\x80r\x1a\x1f\x04\xc8\x9e!\xfa\xbbD\x9f" +
|
"\xfe\x02\xc9_\"y\xd5\xf4\x1a\xac\x02\x90N\xe1V\x80" +
|
||||||
"2)\x8dS\x00\x94\x7f\xe6\xf7y\x8b\xe8\xe7\x88^\x9f" +
|
"\xdc\x8b$?C\xf2IX\x83\x93\x00\xa4\xd3\xf8\x00@" +
|
||||||
"Hc=\x80\xf2\xafx\x0c {\x8e\xe8\xbf!z\x83" +
|
"\xee\x0c\xc9\xdf\"\xf9\xe4\x8a\x1a\x9c\x0c \xbd\xe9\x9d\xe7" +
|
||||||
"\x94\xc6\x06\x00\xe5\x03.\xd7\xaf\x89^+\x848\xd8\x9d" +
|
"u\x92\x9f'\xf9\x94T\x0dN\x01\x90\xde\xc6#\x00\xb9" +
|
||||||
"\x8f\xc31\xb9\xa1\x1e\x95#\xa2i\x87\xae\xc0\xfc\x1e\x16" +
|
"\xf3$\xff\x1d\xc9\xab\x85\x1a\xac\x06\x90\xde\xf7\xee\xf5[" +
|
||||||
"\xbd\\\xd1c&\xa9\x89\xc5d4m\x06\xc4$\xa0[" +
|
"\x92Wr\x11\x0ev\x14\x92pLa\xa8\xc6t\x847" +
|
||||||
"6\xcd\xe2\x8a\xb10\x7f\xb9\x8a\xc8w#H\x9aFw" +
|
"\xac(\x14X\xd0\xdc\xa2_+:\x8d\x0cu\xb7\x98\x89" +
|
||||||
">\x8cK\xcf\xf9\x96\x9b\x90\xc9i\xc5\xeerT#\xd9" +
|
"\xc7\xd0\x80\x98\x01tK\x86\xa1-\x1f\x09\xf3\x131\xa2" +
|
||||||
"\xed\x15\xc7\xac\x94!\x93\xd7\x1c\x96\x0f\x13\xb5U1\x96" +
|
" \x8c c\xe8\x1d\x85(/\xfd\xe0[f@]^" +
|
||||||
"Zf\xa9\x0f\x99U\xd2\x0d\xad\x08\xe1\x97\x89|1Y" +
|
"\xd1:J1G\xb2\x9a\x1d\xdbpJPWPlV" +
|
||||||
"\xa9\xe8\xf9p\xef\x09\x0b;w\x88iN\xc5b6\x89" +
|
"\x88\x0a\xb5\xe9\xe8KL\xa3\xd8\x8d\xcc,\xaa\xba\xa2A" +
|
||||||
"v\x89\x8c+T{t\xa6\xdc\xd2\xa7\x15\xaaF\x11s" +
|
"\xf4\xcbx\xb1\x98q\x1c\xb5\x10\xad=.\xb1s\xfb\x99" +
|
||||||
"\xa3\xf4\x10\xa2\xdd\xbc\x9b\xa3\xec\x90\x8cGaf\xbdV" +
|
"b;&\xb3\xe8j\x17\xa9\xb8\\yD\xd7\x95\x1a\xba" +
|
||||||
"\xac\xb0+)\x06'lnz[\xbd\xe6\xe8r=p" +
|
"\x95\x81\xb2\x19\xc5\x82\xb8<Dh\xb7\xf0\xc6\xb8:d" +
|
||||||
"08\xbb|5\xdf_\x95y\xbd|x\xd1\xdc\xa5#" +
|
"\x92YX\xb7N\xd1\x1cv)dp\xdc\xae\xa7\xab\xd1" +
|
||||||
"\x126\x94\xd5\xf2g1]B\x94\xf3\x02k\x0d\xf9=" +
|
"\xef\x9a&j\x8e\xc3\x89\xda\xc4l\xbe\xa7\xac\xf2\xfa\xf5" +
|
||||||
".dh\xef\x98\xdf\x84\x93M\xdfo\xaeT\x13\x05\xe6" +
|
"p\xd4@\xa6%\xbeltW3\x18\xd2\xb4sq\xcd" +
|
||||||
"x\xbf\xba\x8d!\x93\xca\x03I+\xd9_qu/\xb3" +
|
"\x0b\xbd\xd5\x1f4\xbfPGk'\xe2&\x1ay\x06q" +
|
||||||
"\x93W\xa2\xc5hVy\xf9\xfc\xdd\xd5\xd7\xd7\x13\x8d;" +
|
"s\xa9\x96\x18`\xb6\xffW\x87\xdeo\x10=\x10\x94\xa2" +
|
||||||
"D\x0f\xfco\x0a\xf1\xae\x1d{\x01\xb2m\x14\xb8\xcb1" +
|
"\xf5{~\xdd\xc5\xac\xcc\xa5X1\x1ebN\\\xbf\xdb" +
|
||||||
"\xd4\xa1\xd2\xcdq\xa7\x8b\xc8}\x18U\xbd\x8a\xca\xf1\xa5" +
|
"\xbb\xbb;\xe39\x08\xef\x83\xff\xf5\x11\xde5c\x17@" +
|
||||||
"\x87\xe8\xab1\xea\x8b\x94;9.\xac&\xfa0\xc7\xbb" +
|
"\xae\x89\x12w\x19F6\x94:<\xdci'q7\xc6" +
|
||||||
"v\x0f\xef\x18\xdf>O\xf42\xc7;\xf4\xf0\xae\xc4\xf7" +
|
"\xacW\x92=|\xe9$\xf9*\x8c\xfb\"\xe9;\x1e." +
|
||||||
"/\x12}c\x1c\xef*8:\x06~%\xd1\xc3\xbbm" +
|
"\xac\"\xf9\xa0\x87w\xcd>\xde1o\xf9\x02\xc9K\x1e" +
|
||||||
"\x1c\xa7v\x10}/\xc7\xbb\x84\x87w{\xf0i\x80\xec" +
|
"\xde\xa1\x8fwEo}\x8d\xe4\x1b\x92x\xe7\xe0\xf0\x08" +
|
||||||
"^\xa2\x1f\xe6xW\xe3\xe1\xdd!|\x0e {\x98\xe8" +
|
"\xf8\x15x\x1f\xef\xee\xf6pj\x1b\xc9w{x\x97\xf2" +
|
||||||
"\x8fr\xbc\x9b\xe4\xe1\xddq\xce\xffh\x88\xb3S:<" +
|
"\xf1n\x17>\x09\x90\xdbM\xf2\x83\x1e\xde\xa5}\xbc;" +
|
||||||
"\xbc;\xc1\xf11\xc4Y\xb7b\x15\xb3\x8e\xa5\x1b\x80\x85" +
|
"\x80\xcf\x00\xe4\x0e\x92\xfc\x11\x0f\xef*|\xbc;\xea\xe9" +
|
||||||
"(6r\xe5o3Vn\x87dQ_\xcf\xc2\\\x94" +
|
"?\x12\xe1\xec\xe4\x16\x1f\xef\x8ey\xf8\x18\xe1\xac\xeb\x98" +
|
||||||
"\xd7\xb5\xe2\xe2\x8aV\x84L\xd6\xd1rk\xa3\xd2\xbeh" +
|
"Z\xce6U\x1dp \xce\x8d|\xe9\xdb\x8c\x95\x9a!" +
|
||||||
"wiF\xde\xc6am-\xa3\x0c&\xc5s\xbdS\xb4" +
|
"\xa3\xa9\xebXT\x8b\x0a\xaa\xa2\xb59\x8a\x06u9[" +
|
||||||
"W1K\x1f\x02\x8c\x9a\x81\xb0\xf6I\xf6\x98fuI" +
|
"\xc9\xaf\x89\xa9\xbdf\xb5+z\xc1\xc2Ae\x0d\xa3\x0a" +
|
||||||
"\xc4kJfy\xe0\x17~+i\x1b\xbb\xf3E\xd6\x89" +
|
"&$k\xbd\xadY+\x99\xa9\xf6\x03\xc6\xcd@\xc4}" +
|
||||||
"A\x05$\x1aQ\x06\xd5\xe9\x8bi\x18\xe8\x95%}z" +
|
"2\x9d\x86QN\x89<N\xc9L\x1f\xfc\xa2\xdf\x8a\xca" +
|
||||||
"fl\xbdQ\xf6\xdb\x8b\xa0n\xe9k\xad*H\xd8\xc6" +
|
"\x86\x8e\x82\xc6Z1d@\xbc\x1eWP\x95~1t" +
|
||||||
"2\xcb9\x9d&\x1a\x8enT\xd8E\x1b\xe4\x86+\xc6" +
|
"\x1d}Z\xd2\xad\xd6\x8d\xe4\x1b\xa5\xa0\xbd\x08yKw" +
|
||||||
"Z\x96_\x82F\xce\xcc\xebF\x01.\xeak\xc4KM" +
|
"c\x19!a\x1bJ,o\xb7\x1a\xa8\xdb\xaa\xee\xb0Q" +
|
||||||
"\x99b\x85\x1a\x8ff\x8c=\xd3\xc9sZ@\xe0\xd0E" +
|
"\x0b\xe4\x07\x1d}\x0d+,F=o\x14T}\x00F" +
|
||||||
"e\x87\xdc\x12M\x07Zs|U\xab\xc54;\xd6\xd8" +
|
"\xf55\xfc\xc5\xc6O\x09\xa2\xe6e3&\xde\xef\xc4\xf9" +
|
||||||
"Np\x9a?\x15\xf5\x82\xcc\x9b\x04\xd4\x00\x84OZ\x18" +
|
"\x0d\xc0y\xd0E\xb4Cl\x88\xa7\x03\x8dy\xef\xabF" +
|
||||||
"<\x0b\xc8'6\x81 ?&a\xf4\x9a\x82\xc1\xe3\x89" +
|
"\x93)V\xa2\xb1\x1dg\xb7`\\\xea'\x99?\x09H" +
|
||||||
"\xfc\x90\x05\x82|@B!|k\xc4\xe0\x9dP\xde5" +
|
"\x03Do]\x18\xbe\x17\x88\xc76\x02'>*`\xfc" +
|
||||||
"\x0a\x82\xbcSB1|\xfe\xc3`\xe6.\x8ft\x80 " +
|
"\xcc\x82\xe1\xab\x8a\xf8\xa0\x09\x9c\xb8O@.z\x84\xc4" +
|
||||||
"\x97$L\x84o\xa1\x18\x0c\xece\x8dJ\xab;%\xac" +
|
"\xf0\x01Q\xdc1\x0c\x9c\xb8]@>z\x17\xc4p\x18" +
|
||||||
"\x09\x1f\x161x\x15\x92o\xdb\x0e\x82\xbcDr\x83\x0e" +
|
"\x7f\xc3\xd0$\x04N\xbcK\xc0T\xf4J\x8a\xe1(_" +
|
||||||
"\x0aZ=1\xda\xd0\x0d\x00\x032\x1c2\xda\xd0\x0d\xe6" +
|
"\\K\xdcJ\x150\x1d=9b\xf8^$\xde\xb1\x15" +
|
||||||
"T\x18tZ\x00m\xb8\xc5\x87\xe76t\x83I-$" +
|
"8\xb1Gp\xc3\x16\x0a\x1a\xfd{4\xa1\x1b\"\x06\xd4" +
|
||||||
"s\x9a\xc3\xda\xa8=\xf5>\xa2\x0f\xde\xd0\x86\xf1\x09\xa8" +
|
"y\x98\xd1\x84n8\xc1\xc2\xb0\xd5\x02hB7\x9c\xc3" +
|
||||||
"x\xa9\x9eh\xfc\xda\xba#\xaa\xff\xc2\x11\xd6hT\xfe" +
|
"\xf0\x17\x1b\xc4xZ\xe1|\x172y\xc5fM\xd4\xbb" +
|
||||||
"\x85}\xe8\x9e\xc7\xe3\xa5\xb5?N9\xb4\xdd\x1f\xc6\x9c" +
|
"\xfa\xc0\x8e\x01\xb2C\x13&\xe7\xa6\xfc\xc5\x1a\xa6\xb1\x89" +
|
||||||
"\x8c\x8dSNP\xbd}RD\xf5\x0d!*\x1a\x02\x9f" +
|
"wKL\x0e\xa3\xc1\xd7p\xcc\x0d\xa3&u\xd7cI" +
|
||||||
"\x0e\x86\x86hZAc<\xc1\xec\xd0\xf7|\xbf\xec\xad" +
|
"\xde\x1d\xccZ\x0el\x0d&5\xc7\x13\xb3\x96cD\xc6" +
|
||||||
"\x9e \xbays\x98\x97\xc5\xe8meC\x94\x0e\xe2c" +
|
"\x8f\xf3(\xbf\xca\xc5\x8c\"\x0c\xf8p\xd4\x88\x86\x19v" +
|
||||||
"\xc5\xa9\xb1\xb1\"\x06-\xb94&{\xc4\x87\x8cS/" +
|
"\xcd\xe3L\x1c\x83\xb4\x088q\xf9\xdc\xd1-\x18\x83\x1e" +
|
||||||
"\xd3\xdf\xc5\x1bL\x9e\xce\x12\xdc%\x83GT\x0c\x1e\xbc" +
|
"gF\x7f)\x0b\xe2Z\x91\x1cFNM\x0c#1\xec" +
|
||||||
"e\x99\\\xabAr\x83&\x14\x83\\\x08U&\xbb\xca" +
|
"\xd7\x85\x11\xa5%9\x9a\x9c:A\xf3\x97\xec>\xbdZ" +
|
||||||
"N\xbc\x97e\xfe;\xc9z\x1c\x07\xf1\xceI\x92Gz" +
|
"\x97\xf2\xe25|z\xc5\xf0\x99\\\x14)\xee\xaa\x057" +
|
||||||
"\x02\x85\xfb\xae\x89\x8d\xf6\x8a\xa6\xdfD&W\xc4\xfb\x80" +
|
"\xecP1,\x94P\xe6\xb2\xcbl\xd3\xbbX\xdd\x97\xa9" +
|
||||||
"\x09t\xe5]8\xa8\xda\x93\xb4\x98\xf6\xffZ\xb8\xff\xe9" +
|
"\xe4c\x04\x88\xbfO\x86\xa2\xd5\xbfP\xb4\xee\xea\xc4\xdc" +
|
||||||
"\x99\xb1\xd1[\xe0\x7fo\x12\xf1\x0d\x11\xd5wb\xad\xdd" +
|
"O3\x82\x0e3\xb3<\xd9$\x8cc+\xff\xc0!\xa5" +
|
||||||
"\xdb\xcb\x00\xd4\xb7DT?\x89^\x94>\"G\xfdD" +
|
"\xcf\xd0\xc7\xb4\xfeU\xd1\xfa\xa7\xe7&\xe6ra\xfc\xbd" +
|
||||||
"\xc4\xdeX\x89.\x7fA\x8c\x9fS!\x1bOX5\xf8" +
|
"F\xc2Wy\x94\xdfH\xf4}g\x97\x02\xc8\xaf\xf3(" +
|
||||||
"\x00@\xb6\x96\x12D\x9a'\xac\x84\x97\xb0d\x1c\x04\xc8" +
|
"\x7f\x1c\xbfC]\xa0@\xfd\x98\xc7\xae\x04\x7f\x17?'" +
|
||||||
"\xa6\x88>=^\xa07\xe1\x00@\xb6\x91\xe8\xb3\xd1\xef" +
|
"\xc5\xcf\x88\xe5&\xabY\x1a\xef\x07\xc8UR\xf5\xa8\xf1" +
|
||||||
"\xc8\x83\xd7\xa8\x8a\x15\x81{\xd1,,\xd7\x8dq\xab\xbe" +
|
"\xaaY\xca\xaff\"\xf6\x01\xe4\xb2$\x9f\x99d\xef\xd3" +
|
||||||
"\xe0\x89\x0b\x1d\x82\xcc\x8aE\xb8?\x16_\xbb\x17\xc7\xea" +
|
"\xb1\x17 WK\xf2y\x18\xb4\xeb\xe1\x1b\x96c\xc6\xc8" +
|
||||||
"\xe0p\xec\x84\xcc\xcaR\x88\xe7\xd1\x0e\xc79W1\xfd" +
|
"\xaf\x19\x03\xcbT}LJ\x18>\x8c\xa1Mx\xea\x98" +
|
||||||
"\x9d\xc0\x1cY?\xf8\xbc\xd8\xf3k\x89\xd80\xe0Xl" +
|
"T\x14F\x82oG[\x82$G3)df\x8eR" +
|
||||||
"N\x16\x18C}\xce\x9f?\xdd\x1d3\xc6w\x06\x01\xd4" +
|
"\xbc\x80V4\xeb\xb9\x8c\x99\xf18\xee\xc8\x05\xc9\xe7\xe7" +
|
||||||
"\xd5\"\xaa\xc3\x02\x07(\xb3\xbf\x9c\xd7\xd0aK-\xb6" +
|
"^@4\x12\x93\x82#\x89!Z\xe8\x0c\xf9\x99`8" +
|
||||||
"\xae\xc2$#7\x125\xc5\xd4\x16\xe6\xec~,SA" +
|
"ug\xc2\x19w\xf4\x01\xc8\xabx\x94\x079\x0f\xa0\x8c" +
|
||||||
"\xbe\xd4b\xad\xeb*,\xce\x10\xbcv\x80\xa4\x9b\xf9\x8b" +
|
"\x9eRAA\x9b-1\xd9Z\x87\x09z~(\xee\x98" +
|
||||||
"\x9e9\xc6\xa9,og\x83Y3\xb7\x969c^\x81" +
|
"\xa9g\xcc[=X\"\xb6\xbe\xc4d\x8dk\x1d\x96T" +
|
||||||
"\xaa^*{\xa3\xa7\x8e\xf0\xa1\xb27\xfeP\xe9\xc3\xda" +
|
"\x08\xdfH@P\x8d\xc2\xa8\xc7\x911h\xe7\xad\xac/" +
|
||||||
":r\xf0\xb2\x88\xea\xe6\x18\xac\x8d\x8cF\x1d\xf5\xf8\xa5" +
|
"g\xe4\xd70{\xc4\xdbQ\xd9\xfbfW\xfc@\x12=" +
|
||||||
"\xc4\xffL\xf6\xffJ\x8fuTHKWRd\x86\x7f" +
|
"ov%\x9f7\x03X[K\x01^\xe2Q\xde\x94\x80" +
|
||||||
"D\xf4\x15\x07\xfdW\xda\x13DO\xd0W9\x1c\x83\x10" +
|
"\xb5\xa1\xe1\xb8\xdd\x1e\x9bg\xfc\xdfP\x83\xdf\xeb\x89\x8f" +
|
||||||
"k0\xf6'&t\x88\xe0o\xfe_\x01\x00\x00\xff\xff" +
|
"X\xb6p)\x0c4\xfa\xa7Ge\xf9_\xf5e\x9f\x07" +
|
||||||
"\x86\xbe\xf5t"
|
"\xc2\xe7\x97\x09O\x10\xbdh_\xe6H\x0d\"\x10\xc2\xc4" +
|
||||||
|
"\xbfX\xa1M\xb8`\xf1\xff\x0d\x00\x00\xff\xff\xcb\x86\x19" +
|
||||||
|
"\x8c"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
schemas.Register(schema_db8274f9144abc7e,
|
schemas.Register(schema_db8274f9144abc7e,
|
||||||
|
|
|
@ -299,7 +299,7 @@ gopkg.in/urfave/cli.v2
|
||||||
gopkg.in/urfave/cli.v2/altsrc
|
gopkg.in/urfave/cli.v2/altsrc
|
||||||
# gopkg.in/yaml.v2 v2.2.4
|
# gopkg.in/yaml.v2 v2.2.4
|
||||||
gopkg.in/yaml.v2
|
gopkg.in/yaml.v2
|
||||||
# zombiezen.com/go/capnproto2 v0.0.0-20180616160808-7cfd211c19c7
|
# zombiezen.com/go/capnproto2 v2.18.0+incompatible
|
||||||
zombiezen.com/go/capnproto2
|
zombiezen.com/go/capnproto2
|
||||||
zombiezen.com/go/capnproto2/encoding/text
|
zombiezen.com/go/capnproto2/encoding/text
|
||||||
zombiezen.com/go/capnproto2/internal/fulfiller
|
zombiezen.com/go/capnproto2/internal/fulfiller
|
||||||
|
|
|
@ -10,8 +10,10 @@
|
||||||
|
|
||||||
# Please keep the list sorted.
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Anapaya Systems AG
|
||||||
CloudFlare Inc.
|
CloudFlare Inc.
|
||||||
Daniel Darabos <darabos.daniel@gmail.com>
|
Daniel Darabos <darabos.daniel@gmail.com>
|
||||||
|
Dominik Roos <domi.roos@gmail.com>
|
||||||
Eran Duchan <pavius@gmail.com>
|
Eran Duchan <pavius@gmail.com>
|
||||||
Evan Shaw <edsrzf@gmail.com>
|
Evan Shaw <edsrzf@gmail.com>
|
||||||
Google Inc.
|
Google Inc.
|
||||||
|
@ -20,8 +22,11 @@ James McKaskill <james@foobar.co.nz>
|
||||||
Jason E. Aten <j.e.aten@gmail.com>
|
Jason E. Aten <j.e.aten@gmail.com>
|
||||||
Johan Hernandez <im@bithavoc.io>
|
Johan Hernandez <im@bithavoc.io>
|
||||||
Joonsung Lee <joonsung@devsisters.com>
|
Joonsung Lee <joonsung@devsisters.com>
|
||||||
|
Kiwi.com s.r.o.
|
||||||
Lev Radomislensky <lev.radomislensky@gmail.com>
|
Lev Radomislensky <lev.radomislensky@gmail.com>
|
||||||
Peter Waldschmidt <peterw@gnoso.com>
|
Peter Waldschmidt <peterw@gnoso.com>
|
||||||
|
Tiit Pikma <pikma@hot.ee>
|
||||||
Tom Thorogood <me+github@tomthorogood.co.uk>
|
Tom Thorogood <me+github@tomthorogood.co.uk>
|
||||||
TJ Holowaychuk <tj@apex.sh>
|
TJ Holowaychuk <tj@apex.sh>
|
||||||
William Laffin <william.laffin@gmail.com>
|
William Laffin <william.laffin@gmail.com>
|
||||||
|
Colin Arnott <colin@urandom.co.uk>
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
# Go Cap'n Proto Release Notes
|
# Go Cap'n Proto Release Notes
|
||||||
|
|
||||||
|
## 2.17.3
|
||||||
|
|
||||||
|
- Clear read limits for `const` messages in schemas.
|
||||||
|
([#131](https://github.com/capnproto/go-capnproto2/pull/131))
|
||||||
|
|
||||||
## 2.17.0
|
## 2.17.0
|
||||||
|
|
||||||
- Add `capnp.Canonicalize` function that implements the
|
- Add `capnp.Canonicalize` function that implements the
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
Alan Braithwaite <alan@cloudflare.com>
|
Alan Braithwaite <alan@cloudflare.com>
|
||||||
Albert Strasheim <albert@cloudflare.com>
|
Albert Strasheim <albert@cloudflare.com>
|
||||||
Daniel Darabos <darabos.daniel@gmail.com>
|
Daniel Darabos <darabos.daniel@gmail.com>
|
||||||
|
Dominik Roos <domi.roos@gmail.com>
|
||||||
Eran Duchan <pavius@gmail.com>
|
Eran Duchan <pavius@gmail.com>
|
||||||
Evan Shaw <edsrzf@gmail.com>
|
Evan Shaw <edsrzf@gmail.com>
|
||||||
Ian Denhardt <ian@zenhack.net>
|
Ian Denhardt <ian@zenhack.net>
|
||||||
|
@ -26,8 +27,13 @@ Jason E. Aten <j.e.aten@gmail.com>
|
||||||
Johan Hernandez <im@bithavoc.io>
|
Johan Hernandez <im@bithavoc.io>
|
||||||
Joonsung Lee <joonsung@devsisters.com>
|
Joonsung Lee <joonsung@devsisters.com>
|
||||||
Lev Radomislensky <lev.radomislensky@gmail.com>
|
Lev Radomislensky <lev.radomislensky@gmail.com>
|
||||||
|
Lukas Vogel <vogel@anapaya.net> <lukedirtwalker@gmail.com>
|
||||||
|
Martin Sucha <martin.sucha@kiwi.com>
|
||||||
Peter Waldschmidt <peterw@gnoso.com>
|
Peter Waldschmidt <peterw@gnoso.com>
|
||||||
Ross Light <light@google.com> <ross@zombiezen.com>
|
Ross Light <light@google.com> <ross@zombiezen.com>
|
||||||
|
Stephen Shirley <kormat@anapaya.net> <kormat@gmail.com>
|
||||||
|
Tiit Pikma <pikma@hot.ee>
|
||||||
Tom Thorogood <me+github@tomthorogood.co.uk>
|
Tom Thorogood <me+github@tomthorogood.co.uk>
|
||||||
TJ Holowaychuk <tj@apex.sh>
|
TJ Holowaychuk <tj@apex.sh>
|
||||||
William Laffin <william.laffin@gmail.com>
|
William Laffin <william.laffin@gmail.com>
|
||||||
|
Colin Arnott <colin@urandom.co.uk>
|
||||||
|
|
|
@ -4,17 +4,17 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
name = "io_bazel_rules_go",
|
name = "io_bazel_rules_go",
|
||||||
sha256 = "8b68d0630d63d95dacc0016c3bb4b76154fe34fca93efd65d1c366de3fcb4294",
|
sha256 = "86ae934bd4c43b99893fc64be9d9fc684b81461581df7ea8fc291c816f5ee8c5",
|
||||||
urls = ["https://github.com/bazelbuild/rules_go/releases/download/0.12.1/rules_go-0.12.1.tar.gz"],
|
urls = ["https://github.com/bazelbuild/rules_go/releases/download/0.18.3/rules_go-0.18.3.tar.gz"],
|
||||||
)
|
)
|
||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
name = "bazel_gazelle",
|
name = "bazel_gazelle",
|
||||||
sha256 = "ddedc7aaeb61f2654d7d7d4fd7940052ea992ccdb031b8f9797ed143ac7e8d43",
|
sha256 = "3c681998538231a2d24d0c07ed5a7658cb72bfb5fd4bf9911157c0e9ac6a2687",
|
||||||
urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/0.12.0/bazel-gazelle-0.12.0.tar.gz"],
|
urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/0.17.0/bazel-gazelle-0.17.0.tar.gz"],
|
||||||
)
|
)
|
||||||
|
|
||||||
load("@io_bazel_rules_go//go:def.bzl", "go_register_toolchains", "go_rules_dependencies")
|
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
|
||||||
|
|
||||||
go_rules_dependencies()
|
go_rules_dependencies()
|
||||||
|
|
||||||
|
|
|
@ -200,6 +200,9 @@ func (s *Segment) readListPtr(base Address, val rawPointer) (List, error) {
|
||||||
}
|
}
|
||||||
sz := hdr.structSize()
|
sz := hdr.structSize()
|
||||||
n := int32(hdr.offset())
|
n := int32(hdr.offset())
|
||||||
|
if n < 0 {
|
||||||
|
return List{}, errListSize
|
||||||
|
}
|
||||||
// TODO(light): check that this has the same end address
|
// TODO(light): check that this has the same end address
|
||||||
if tsize, ok := sz.totalSize().times(n); !ok {
|
if tsize, ok := sz.totalSize().times(n); !ok {
|
||||||
return List{}, errOverflow
|
return List{}, errOverflow
|
||||||
|
@ -214,11 +217,15 @@ func (s *Segment) readListPtr(base Address, val rawPointer) (List, error) {
|
||||||
flags: isCompositeList,
|
flags: isCompositeList,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
n := val.numListElements()
|
||||||
|
if n < 0 {
|
||||||
|
return List{}, errListSize
|
||||||
|
}
|
||||||
if lt == bit1List {
|
if lt == bit1List {
|
||||||
return List{
|
return List{
|
||||||
seg: s,
|
seg: s,
|
||||||
off: addr,
|
off: addr,
|
||||||
length: val.numListElements(),
|
length: n,
|
||||||
flags: isBitList,
|
flags: isBitList,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -226,7 +233,7 @@ func (s *Segment) readListPtr(base Address, val rawPointer) (List, error) {
|
||||||
seg: s,
|
seg: s,
|
||||||
size: val.elementSize(),
|
size: val.elementSize(),
|
||||||
off: addr,
|
off: addr,
|
||||||
length: val.numListElements(),
|
length: n,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,10 +81,10 @@ Structs
|
||||||
|
|
||||||
For the following schema:
|
For the following schema:
|
||||||
|
|
||||||
struct Foo @0x8423424e9b01c0af {
|
struct Foo @0x8423424e9b01c0af {
|
||||||
num @0 :UInt32;
|
num @0 :UInt32;
|
||||||
bar @1 :Foo;
|
bar @1 :Foo;
|
||||||
}
|
}
|
||||||
|
|
||||||
capnpc-go will generate:
|
capnpc-go will generate:
|
||||||
|
|
||||||
|
@ -168,9 +168,9 @@ For each group a typedef is created with a different method set for just the
|
||||||
groups fields:
|
groups fields:
|
||||||
|
|
||||||
struct Foo {
|
struct Foo {
|
||||||
group :Group {
|
group :Group {
|
||||||
field @0 :Bool;
|
field @0 :Bool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generates the following:
|
generates the following:
|
||||||
|
@ -194,10 +194,10 @@ Named unions are treated as a group with an inner unnamed union. Unnamed
|
||||||
unions generate an enum Type_Which and a corresponding Which() function:
|
unions generate an enum Type_Which and a corresponding Which() function:
|
||||||
|
|
||||||
struct Foo {
|
struct Foo {
|
||||||
union {
|
union {
|
||||||
a @0 :Bool;
|
a @0 :Bool;
|
||||||
b @1 :Bool;
|
b @1 :Bool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generates the following:
|
generates the following:
|
||||||
|
@ -225,10 +225,10 @@ For voids in unions, there is a void setter that just sets the discriminator.
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
struct Foo {
|
struct Foo {
|
||||||
union {
|
union {
|
||||||
a @0 :Void;
|
a @0 :Void;
|
||||||
b @1 :Void;
|
b @1 :Void;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generates the following:
|
generates the following:
|
||||||
|
@ -241,14 +241,14 @@ the discriminator. This must be called before the group getter can be
|
||||||
used to set values. For example:
|
used to set values. For example:
|
||||||
|
|
||||||
struct Foo {
|
struct Foo {
|
||||||
union {
|
union {
|
||||||
a :group {
|
a :group {
|
||||||
v :Bool
|
v :Bool
|
||||||
}
|
}
|
||||||
b :group {
|
b :group {
|
||||||
v :Bool
|
v :Bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
and in usage:
|
and in usage:
|
||||||
|
@ -293,10 +293,10 @@ but the tags can be customized with a $Go.tag or $Go.notag annotation.
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
enum ElementSize {
|
enum ElementSize {
|
||||||
empty @0 $Go.tag("void");
|
empty @0 $Go.tag("void");
|
||||||
bit @1 $Go.tag("1 bit");
|
bit @1 $Go.tag("1 bit");
|
||||||
byte @2 $Go.tag("8 bits");
|
byte @2 $Go.tag("8 bits");
|
||||||
inlineComposite @7 $Go.notag;
|
inlineComposite @7 $Go.notag;
|
||||||
}
|
}
|
||||||
|
|
||||||
In the generated go file:
|
In the generated go file:
|
||||||
|
|
|
@ -42,14 +42,14 @@ func MarshalList(typeID uint64, l capnp.List) (string, error) {
|
||||||
|
|
||||||
// An Encoder writes the text format of Cap'n Proto messages to an output stream.
|
// An Encoder writes the text format of Cap'n Proto messages to an output stream.
|
||||||
type Encoder struct {
|
type Encoder struct {
|
||||||
w errWriter
|
w indentWriter
|
||||||
tmp []byte
|
tmp []byte
|
||||||
nodes nodemap.Map
|
nodes nodemap.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEncoder returns a new encoder that writes to w.
|
// NewEncoder returns a new encoder that writes to w.
|
||||||
func NewEncoder(w io.Writer) *Encoder {
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
return &Encoder{w: errWriter{w: w}}
|
return &Encoder{w: indentWriter{w: w}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseRegistry changes the registry that the encoder consults for
|
// UseRegistry changes the registry that the encoder consults for
|
||||||
|
@ -58,6 +58,12 @@ func (enc *Encoder) UseRegistry(reg *schemas.Registry) {
|
||||||
enc.nodes.UseRegistry(reg)
|
enc.nodes.UseRegistry(reg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetIndent sets string to indent each level with.
|
||||||
|
// An empty string disables indentation.
|
||||||
|
func (enc *Encoder) SetIndent(indent string) {
|
||||||
|
enc.w.indentPerLevel = indent
|
||||||
|
}
|
||||||
|
|
||||||
// Encode writes the text representation of s to the stream.
|
// Encode writes the text representation of s to the stream.
|
||||||
func (enc *Encoder) Encode(typeID uint64, s capnp.Struct) error {
|
func (enc *Encoder) Encode(typeID uint64, s capnp.Struct) error {
|
||||||
if enc.w.err != nil {
|
if enc.w.err != nil {
|
||||||
|
@ -133,8 +139,14 @@ func (enc *Encoder) marshalStruct(typeID uint64, s capnp.Struct) error {
|
||||||
if n.StructNode().DiscriminantCount() > 0 {
|
if n.StructNode().DiscriminantCount() > 0 {
|
||||||
discriminant = s.Uint16(capnp.DataOffset(n.StructNode().DiscriminantOffset() * 2))
|
discriminant = s.Uint16(capnp.DataOffset(n.StructNode().DiscriminantOffset() * 2))
|
||||||
}
|
}
|
||||||
enc.w.WriteByte('(')
|
|
||||||
fields := codeOrderFields(n.StructNode())
|
fields := codeOrderFields(n.StructNode())
|
||||||
|
if len(fields) == 0 {
|
||||||
|
enc.w.WriteString("()")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
enc.w.WriteByte('(')
|
||||||
|
enc.w.Indent()
|
||||||
|
enc.w.NewLine()
|
||||||
first := true
|
first := true
|
||||||
for _, f := range fields {
|
for _, f := range fields {
|
||||||
if !(f.Which() == schema.Field_Which_slot || f.Which() == schema.Field_Which_group) {
|
if !(f.Which() == schema.Field_Which_slot || f.Which() == schema.Field_Which_group) {
|
||||||
|
@ -144,7 +156,8 @@ func (enc *Encoder) marshalStruct(typeID uint64, s capnp.Struct) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !first {
|
if !first {
|
||||||
enc.w.WriteString(", ")
|
enc.w.WriteByte(',')
|
||||||
|
enc.w.NewLineOrSpace()
|
||||||
}
|
}
|
||||||
first = false
|
first = false
|
||||||
name, err := f.NameBytes()
|
name, err := f.NameBytes()
|
||||||
|
@ -164,6 +177,8 @@ func (enc *Encoder) marshalStruct(typeID uint64, s capnp.Struct) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
enc.w.NewLine()
|
||||||
|
enc.w.Unindent()
|
||||||
enc.w.WriteByte(')')
|
enc.w.WriteByte(')')
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -298,101 +313,154 @@ func codeOrderFields(s schema.Node_structNode) []schema.Field {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (enc *Encoder) marshalList(elem schema.Type, l capnp.List) error {
|
func (enc *Encoder) marshalList(elem schema.Type, l capnp.List) error {
|
||||||
switch elem.Which() {
|
writeListItems := func(writeItem func(i int) error) error {
|
||||||
case schema.Type_Which_void:
|
if l.Len() == 0 {
|
||||||
enc.w.WriteString(capnp.VoidList{List: l}.String())
|
_, err := enc.w.WriteString("[]")
|
||||||
case schema.Type_Which_bool:
|
return err
|
||||||
enc.w.WriteString(capnp.BitList{List: l}.String())
|
}
|
||||||
case schema.Type_Which_int8:
|
|
||||||
enc.w.WriteString(capnp.Int8List{List: l}.String())
|
|
||||||
case schema.Type_Which_int16:
|
|
||||||
enc.w.WriteString(capnp.Int16List{List: l}.String())
|
|
||||||
case schema.Type_Which_int32:
|
|
||||||
enc.w.WriteString(capnp.Int32List{List: l}.String())
|
|
||||||
case schema.Type_Which_int64:
|
|
||||||
enc.w.WriteString(capnp.Int64List{List: l}.String())
|
|
||||||
case schema.Type_Which_uint8:
|
|
||||||
enc.w.WriteString(capnp.UInt8List{List: l}.String())
|
|
||||||
case schema.Type_Which_uint16:
|
|
||||||
enc.w.WriteString(capnp.UInt16List{List: l}.String())
|
|
||||||
case schema.Type_Which_uint32:
|
|
||||||
enc.w.WriteString(capnp.UInt32List{List: l}.String())
|
|
||||||
case schema.Type_Which_uint64:
|
|
||||||
enc.w.WriteString(capnp.UInt64List{List: l}.String())
|
|
||||||
case schema.Type_Which_float32:
|
|
||||||
enc.w.WriteString(capnp.Float32List{List: l}.String())
|
|
||||||
case schema.Type_Which_float64:
|
|
||||||
enc.w.WriteString(capnp.Float64List{List: l}.String())
|
|
||||||
case schema.Type_Which_data:
|
|
||||||
enc.w.WriteString(capnp.DataList{List: l}.String())
|
|
||||||
case schema.Type_Which_text:
|
|
||||||
enc.w.WriteString(capnp.TextList{List: l}.String())
|
|
||||||
case schema.Type_Which_structType:
|
|
||||||
enc.w.WriteByte('[')
|
enc.w.WriteByte('[')
|
||||||
|
enc.w.Indent()
|
||||||
|
enc.w.NewLine()
|
||||||
for i := 0; i < l.Len(); i++ {
|
for i := 0; i < l.Len(); i++ {
|
||||||
if i > 0 {
|
err := writeItem(i)
|
||||||
enc.w.WriteString(", ")
|
|
||||||
}
|
|
||||||
err := enc.marshalStruct(elem.StructType().TypeId(), l.Struct(i))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if i == l.Len()-1 {
|
||||||
|
enc.w.NewLine()
|
||||||
|
} else {
|
||||||
|
enc.w.WriteByte(',')
|
||||||
|
enc.w.NewLineOrSpace()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
enc.w.Unindent()
|
||||||
enc.w.WriteByte(']')
|
enc.w.WriteByte(']')
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
writeListItemsN := func(writeItem func(i int) (int, error)) error {
|
||||||
|
return writeListItems(func(i int) error {
|
||||||
|
_, err := writeItem(i)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
switch elem.Which() {
|
||||||
|
case schema.Type_Which_void:
|
||||||
|
return writeListItemsN(func(_ int) (int, error) {
|
||||||
|
return enc.w.WriteString("void")
|
||||||
|
})
|
||||||
|
case schema.Type_Which_bool:
|
||||||
|
p := capnp.BitList{List: l}
|
||||||
|
return writeListItemsN(func(i int) (int, error) {
|
||||||
|
if p.At(i) {
|
||||||
|
return enc.w.WriteString("true")
|
||||||
|
} else {
|
||||||
|
return enc.w.WriteString("false")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
case schema.Type_Which_int8:
|
||||||
|
p := capnp.Int8List{List: l}
|
||||||
|
return writeListItemsN(func(i int) (int, error) {
|
||||||
|
return enc.w.WriteString(strconv.FormatInt(int64(p.At(i)), 10))
|
||||||
|
})
|
||||||
|
case schema.Type_Which_int16:
|
||||||
|
p := capnp.Int16List{List: l}
|
||||||
|
return writeListItemsN(func(i int) (int, error) {
|
||||||
|
return enc.w.WriteString(strconv.FormatInt(int64(p.At(i)), 10))
|
||||||
|
})
|
||||||
|
case schema.Type_Which_int32:
|
||||||
|
p := capnp.Int32List{List: l}
|
||||||
|
return writeListItemsN(func(i int) (int, error) {
|
||||||
|
return enc.w.WriteString(strconv.FormatInt(int64(p.At(i)), 10))
|
||||||
|
})
|
||||||
|
case schema.Type_Which_int64:
|
||||||
|
p := capnp.Int64List{List: l}
|
||||||
|
return writeListItemsN(func(i int) (int, error) {
|
||||||
|
return enc.w.WriteString(strconv.FormatInt(p.At(i), 10))
|
||||||
|
})
|
||||||
|
case schema.Type_Which_uint8:
|
||||||
|
p := capnp.UInt8List{List: l}
|
||||||
|
return writeListItemsN(func(i int) (int, error) {
|
||||||
|
return enc.w.WriteString(strconv.FormatUint(uint64(p.At(i)), 10))
|
||||||
|
})
|
||||||
|
case schema.Type_Which_uint16:
|
||||||
|
p := capnp.UInt16List{List: l}
|
||||||
|
return writeListItemsN(func(i int) (int, error) {
|
||||||
|
return enc.w.WriteString(strconv.FormatUint(uint64(p.At(i)), 10))
|
||||||
|
})
|
||||||
|
case schema.Type_Which_uint32:
|
||||||
|
p := capnp.UInt32List{List: l}
|
||||||
|
return writeListItemsN(func(i int) (int, error) {
|
||||||
|
return enc.w.WriteString(strconv.FormatUint(uint64(p.At(i)), 10))
|
||||||
|
})
|
||||||
|
case schema.Type_Which_uint64:
|
||||||
|
p := capnp.UInt64List{List: l}
|
||||||
|
return writeListItemsN(func(i int) (int, error) {
|
||||||
|
return enc.w.WriteString(strconv.FormatUint(p.At(i), 10))
|
||||||
|
})
|
||||||
|
case schema.Type_Which_float32:
|
||||||
|
p := capnp.Float32List{List: l}
|
||||||
|
return writeListItemsN(func(i int) (int, error) {
|
||||||
|
return enc.w.WriteString(strconv.FormatFloat(float64(p.At(i)), 'g', -1, 32))
|
||||||
|
})
|
||||||
|
case schema.Type_Which_float64:
|
||||||
|
p := capnp.Float64List{List: l}
|
||||||
|
return writeListItemsN(func(i int) (int, error) {
|
||||||
|
return enc.w.WriteString(strconv.FormatFloat(p.At(i), 'g', -1, 64))
|
||||||
|
})
|
||||||
|
case schema.Type_Which_data:
|
||||||
|
p := capnp.DataList{List: l}
|
||||||
|
return writeListItemsN(func(i int) (int, error) {
|
||||||
|
s, err := p.At(i)
|
||||||
|
if err != nil {
|
||||||
|
return enc.w.WriteString("<error>")
|
||||||
|
}
|
||||||
|
buf := strquote.Append(nil, s)
|
||||||
|
return enc.w.Write(buf)
|
||||||
|
})
|
||||||
|
case schema.Type_Which_text:
|
||||||
|
p := capnp.TextList{List: l}
|
||||||
|
return writeListItemsN(func(i int) (int, error) {
|
||||||
|
s, err := p.BytesAt(i)
|
||||||
|
if err != nil {
|
||||||
|
return enc.w.WriteString("<error>")
|
||||||
|
}
|
||||||
|
buf := strquote.Append(nil, s)
|
||||||
|
return enc.w.Write(buf)
|
||||||
|
})
|
||||||
|
case schema.Type_Which_structType:
|
||||||
|
return writeListItems(func(i int) error {
|
||||||
|
return enc.marshalStruct(elem.StructType().TypeId(), l.Struct(i))
|
||||||
|
})
|
||||||
case schema.Type_Which_list:
|
case schema.Type_Which_list:
|
||||||
enc.w.WriteByte('[')
|
|
||||||
ee, err := elem.List().ElementType()
|
ee, err := elem.List().ElementType()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for i := 0; i < l.Len(); i++ {
|
return writeListItems(func(i int) error {
|
||||||
if i > 0 {
|
|
||||||
enc.w.WriteString(", ")
|
|
||||||
}
|
|
||||||
p, err := capnp.PointerList{List: l}.PtrAt(i)
|
p, err := capnp.PointerList{List: l}.PtrAt(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = enc.marshalList(ee, p.List())
|
return enc.marshalList(ee, p.List())
|
||||||
if err != nil {
|
})
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
enc.w.WriteByte(']')
|
|
||||||
case schema.Type_Which_enum:
|
case schema.Type_Which_enum:
|
||||||
enc.w.WriteByte('[')
|
|
||||||
il := capnp.UInt16List{List: l}
|
il := capnp.UInt16List{List: l}
|
||||||
typ := elem.Enum().TypeId()
|
typ := elem.Enum().TypeId()
|
||||||
// TODO(light): only search for node once
|
// TODO(light): only search for node once
|
||||||
for i := 0; i < il.Len(); i++ {
|
return writeListItems(func(i int) error {
|
||||||
if i > 0 {
|
return enc.marshalEnum(typ, il.At(i))
|
||||||
enc.w.WriteString(", ")
|
})
|
||||||
}
|
|
||||||
enc.marshalEnum(typ, il.At(i))
|
|
||||||
}
|
|
||||||
enc.w.WriteByte(']')
|
|
||||||
case schema.Type_Which_interface:
|
case schema.Type_Which_interface:
|
||||||
enc.w.WriteByte('[')
|
return writeListItemsN(func(_ int) (int, error) {
|
||||||
for i := 0; i < l.Len(); i++ {
|
return enc.w.WriteString(interfaceMarker)
|
||||||
if i > 0 {
|
})
|
||||||
enc.w.WriteString(", ")
|
|
||||||
}
|
|
||||||
enc.w.WriteString(interfaceMarker)
|
|
||||||
}
|
|
||||||
enc.w.WriteByte(']')
|
|
||||||
case schema.Type_Which_anyPointer:
|
case schema.Type_Which_anyPointer:
|
||||||
enc.w.WriteByte('[')
|
return writeListItemsN(func(_ int) (int, error) {
|
||||||
for i := 0; i < l.Len(); i++ {
|
return enc.w.WriteString(anyPointerMarker)
|
||||||
if i > 0 {
|
})
|
||||||
enc.w.WriteString(", ")
|
|
||||||
}
|
|
||||||
enc.w.WriteString(anyPointerMarker)
|
|
||||||
}
|
|
||||||
enc.w.WriteByte(']')
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown list type %v", elem.Which())
|
return fmt.Errorf("unknown list type %v", elem.Which())
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (enc *Encoder) marshalEnum(typ uint64, val uint16) error {
|
func (enc *Encoder) marshalEnum(typ uint64, val uint16) error {
|
||||||
|
@ -419,37 +487,96 @@ func (enc *Encoder) marshalEnum(typ uint64, val uint16) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type errWriter struct {
|
// indentWriter is helper for writing indented text
|
||||||
|
type indentWriter struct {
|
||||||
w io.Writer
|
w io.Writer
|
||||||
err error
|
err error
|
||||||
|
|
||||||
|
// indentPerLevel is a string to prepend to a line for every level of indentation.
|
||||||
|
indentPerLevel string
|
||||||
|
|
||||||
|
// current indent level
|
||||||
|
currentIndent int
|
||||||
|
|
||||||
|
// hasLineContent is true when we have written something on the current line.
|
||||||
|
hasLineContent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ew *errWriter) Write(p []byte) (int, error) {
|
func (iw *indentWriter) beforeWrite() {
|
||||||
if ew.err != nil {
|
if iw.err != nil {
|
||||||
return 0, ew.err
|
return
|
||||||
|
}
|
||||||
|
if len(iw.indentPerLevel) > 0 && !iw.hasLineContent {
|
||||||
|
iw.hasLineContent = true
|
||||||
|
for i := 0; i < iw.currentIndent; i++ {
|
||||||
|
_, err := iw.w.Write([]byte(iw.indentPerLevel))
|
||||||
|
if err != nil {
|
||||||
|
iw.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iw *indentWriter) Write(p []byte) (int, error) {
|
||||||
|
iw.beforeWrite()
|
||||||
|
if iw.err != nil {
|
||||||
|
return 0, iw.err
|
||||||
}
|
}
|
||||||
var n int
|
var n int
|
||||||
n, ew.err = ew.w.Write(p)
|
n, iw.err = iw.w.Write(p)
|
||||||
return n, ew.err
|
return n, iw.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ew *errWriter) WriteString(s string) (int, error) {
|
func (iw *indentWriter) WriteString(s string) (int, error) {
|
||||||
if ew.err != nil {
|
iw.beforeWrite()
|
||||||
return 0, ew.err
|
if iw.err != nil {
|
||||||
|
return 0, iw.err
|
||||||
}
|
}
|
||||||
var n int
|
var n int
|
||||||
n, ew.err = io.WriteString(ew.w, s)
|
n, iw.err = io.WriteString(iw.w, s)
|
||||||
return n, ew.err
|
return n, iw.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ew *errWriter) WriteByte(b byte) error {
|
func (iw *indentWriter) WriteByte(b byte) error {
|
||||||
if ew.err != nil {
|
iw.beforeWrite()
|
||||||
return ew.err
|
if iw.err != nil {
|
||||||
|
return iw.err
|
||||||
}
|
}
|
||||||
if bw, ok := ew.w.(io.ByteWriter); ok {
|
if bw, ok := iw.w.(io.ByteWriter); ok {
|
||||||
ew.err = bw.WriteByte(b)
|
iw.err = bw.WriteByte(b)
|
||||||
} else {
|
} else {
|
||||||
_, ew.err = ew.w.Write([]byte{b})
|
_, iw.err = iw.w.Write([]byte{b})
|
||||||
|
}
|
||||||
|
return iw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iw *indentWriter) Indent() {
|
||||||
|
iw.currentIndent++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iw *indentWriter) Unindent() {
|
||||||
|
iw.currentIndent--
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iw *indentWriter) NewLine() {
|
||||||
|
if len(iw.indentPerLevel) > 0 && iw.hasLineContent {
|
||||||
|
if iw.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if bw, ok := iw.w.(io.ByteWriter); ok {
|
||||||
|
iw.err = bw.WriteByte('\n')
|
||||||
|
} else {
|
||||||
|
_, iw.err = iw.w.Write([]byte{'\n'})
|
||||||
|
}
|
||||||
|
iw.hasLineContent = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iw *indentWriter) NewLineOrSpace() {
|
||||||
|
if len(iw.indentPerLevel) > 0 {
|
||||||
|
iw.NewLine()
|
||||||
|
} else {
|
||||||
|
iw.WriteByte(' ')
|
||||||
}
|
}
|
||||||
return ew.err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
module "zombiezen.com/go/capnproto2"
|
|
||||||
|
|
||||||
require (
|
|
||||||
"github.com/kylelemons/godebug" v0.0.0-20170820004349-d65d576e9348
|
|
||||||
"golang.org/x/net" v0.0.0-20180218175443-cbe0f9307d01
|
|
||||||
)
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
|
||||||
|
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
|
github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
|
||||||
|
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01 h1:po1f06KS05FvIQQA2pMuOWZAUXiy1KYdIf0ElUU2Hhc=
|
||||||
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
@ -142,6 +142,9 @@ func Unpack(dst, src []byte) ([]byte, error) {
|
||||||
dst = allocWords(dst, int(src[0]))
|
dst = allocWords(dst, int(src[0]))
|
||||||
src = src[1:]
|
src = src[1:]
|
||||||
n := copy(dst[start:], src)
|
n := copy(dst[start:], src)
|
||||||
|
if n < len(dst)-start {
|
||||||
|
return dst, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
src = src[n:]
|
src = src[n:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,22 +284,22 @@ func (r *Reader) ReadWord(p []byte) error {
|
||||||
switch tag {
|
switch tag {
|
||||||
case zeroTag:
|
case zeroTag:
|
||||||
z, err := r.rd.ReadByte()
|
z, err := r.rd.ReadByte()
|
||||||
if err == io.EOF {
|
if err != nil {
|
||||||
r.err = io.ErrUnexpectedEOF
|
if err == io.EOF {
|
||||||
return nil
|
err = io.ErrUnexpectedEOF
|
||||||
} else if err != nil {
|
}
|
||||||
r.err = err
|
r.err = err
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
r.zeroes = int(z)
|
r.zeroes = int(z)
|
||||||
case unpackedTag:
|
case unpackedTag:
|
||||||
l, err := r.rd.ReadByte()
|
l, err := r.rd.ReadByte()
|
||||||
if err == io.EOF {
|
if err != nil {
|
||||||
r.err = io.ErrUnexpectedEOF
|
if err == io.EOF {
|
||||||
return nil
|
err = io.ErrUnexpectedEOF
|
||||||
} else if err != nil {
|
}
|
||||||
r.err = err
|
r.err = err
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
r.literal = int(l)
|
r.literal = int(l)
|
||||||
}
|
}
|
||||||
|
|
|
@ -360,7 +360,7 @@ func (ssa *singleSegmentArena) Allocate(sz Size, segs map[SegmentID]*Segment) (S
|
||||||
if hasCapacity(data, sz) {
|
if hasCapacity(data, sz) {
|
||||||
return 0, data, nil
|
return 0, data, nil
|
||||||
}
|
}
|
||||||
inc, err := nextAlloc(int64(len(data)), int64(maxSegmentSize()), sz)
|
inc, err := nextAlloc(int64(cap(data)), int64(maxSegmentSize()), sz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, fmt.Errorf("capnp: alloc %d bytes: %v", sz, err)
|
return 0, nil, fmt.Errorf("capnp: alloc %d bytes: %v", sz, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,7 +155,7 @@ func (p Ptr) text() (b []byte, ok bool) {
|
||||||
// Text must be null-terminated.
|
// Text must be null-terminated.
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
return b[:len(b)-1 : len(b)], true
|
return b[: len(b)-1 : len(b)], true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data attempts to convert p into Data, returning nil if p is not a
|
// Data attempts to convert p into Data, returning nil if p is not a
|
||||||
|
|
|
@ -112,7 +112,7 @@ func (r *record) read() ([]byte, error) {
|
||||||
}
|
}
|
||||||
p := packed.NewReader(bufio.NewReader(z))
|
p := packed.NewReader(bufio.NewReader(z))
|
||||||
r.data, r.err = ioutil.ReadAll(p)
|
r.data, r.err = ioutil.ReadAll(p)
|
||||||
if err != nil {
|
if r.err != nil {
|
||||||
r.data = nil
|
r.data = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue