From 974c570320d14cc2ff9dfbe2d9e76b7e1eb99acb Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 26 Jun 2024 15:26:07 +0000 Subject: [PATCH 01/19] chore: add DRPC tailnet & cli implementation --- cli/ping.go | 3 + cli/portforward.go | 3 + cli/root.go | 55 +++-- cli/speedtest.go | 3 + cli/ssh.go | 5 +- cli/testdata/coder_--help.golden | 3 + codersdk/workspacesdk/connector.go | 76 ++++--- .../workspacesdk/connector_internal_test.go | 3 +- codersdk/workspacesdk/workspacesdk.go | 63 +++--- docs/cli.md | 9 + enterprise/cli/testdata/coder_--help.golden | 3 + tailnet/conn.go | 18 ++ tailnet/logger.go | 200 ++++++++++++++++++ tailnet/logger_internal_test.go | 145 +++++++++++++ 14 files changed, 518 insertions(+), 71 deletions(-) create mode 100644 tailnet/logger.go create mode 100644 tailnet/logger_internal_test.go diff --git a/cli/ping.go b/cli/ping.go index 82becb016bde7..ff6c318cc1b84 100644 --- a/cli/ping.go +++ b/cli/ping.go @@ -58,6 +58,9 @@ func (r *RootCmd) ping() *serpent.Command { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") opts.BlockEndpoints = true } + if r.noNetworkTelemetry { + opts.EnableTelemetry = true + } conn, err := workspacesdk.New(client).DialAgent(ctx, workspaceAgent.ID, opts) if err != nil { return err diff --git a/cli/portforward.go b/cli/portforward.go index 4c0b1d772eecc..bef0bb6f12be1 100644 --- a/cli/portforward.go +++ b/cli/portforward.go @@ -106,6 +106,9 @@ func (r *RootCmd) portForward() *serpent.Command { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") opts.BlockEndpoints = true } + if r.noNetworkTelemetry { + opts.EnableTelemetry = true + } conn, err := workspacesdk.New(client).DialAgent(ctx, workspaceAgent.ID, opts) if err != nil { return err diff --git a/cli/root.go b/cli/root.go index 4d95aff30e398..20818a7d716d5 100644 --- a/cli/root.go +++ b/cli/root.go @@ -52,6 +52,7 @@ var ( ) const ( +<<<<<<< HEAD varURL = "url" varToken = "token" varAgentToken = "agent-token" @@ -65,6 +66,23 @@ const ( varForceTty = "force-tty" varVerbose = "verbose" varDisableDirect = "disable-direct-connections" +======= + varURL = "url" + varToken = "token" + varAgentToken = "agent-token" + varAgentTokenFile = "agent-token-file" + varAgentURL = "agent-url" + varHeader = "header" + varHeaderCommand = "header-command" + varNoOpen = "no-open" + varNoVersionCheck = "no-version-warning" + varNoFeatureWarning = "no-feature-warning" + varForceTty = "force-tty" + varVerbose = "verbose" + varOrganizationSelect = "organization" + varDisableDirect = "disable-direct-connections" + varDisableNetworkTelemetry = "disable-network-telemetry" +>>>>>>> 365c3bc71 (chore: add DRPC tailnet & cli implementation) notLoggedInMessage = "You are not logged in. Try logging in using 'coder login '." @@ -435,6 +453,13 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err Value: serpent.BoolOf(&r.disableDirect), Group: globalGroup, }, + { + Flag: varDisableNetworkTelemetry, + Env: "CODER_DISABLE_NETWORK_TELEMETRY", + Description: "Disable network telemetry.", + Value: serpent.BoolOf(&r.noNetworkTelemetry), + Group: globalGroup, + }, { Flag: "debug-http", Description: "Debug codersdk HTTP requests.", @@ -466,20 +491,22 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err // RootCmd contains parameters and helpers useful to all commands. type RootCmd struct { - clientURL *url.URL - token string - globalConfig string - header []string - headerCommand string - agentToken string - agentTokenFile string - agentURL *url.URL - forceTTY bool - noOpen bool - verbose bool - versionFlag bool - disableDirect bool - debugHTTP bool + clientURL *url.URL + token string + globalConfig string + header []string + headerCommand string + agentToken string + agentTokenFile string + agentURL *url.URL + forceTTY bool + noOpen bool + verbose bool + organizationSelect string + versionFlag bool + disableDirect bool + debugHTTP bool + noNetworkTelemetry bool noVersionCheck bool noFeatureWarning bool diff --git a/cli/speedtest.go b/cli/speedtest.go index 42fe7604c6dc4..3f34e5796f66f 100644 --- a/cli/speedtest.go +++ b/cli/speedtest.go @@ -102,6 +102,9 @@ func (r *RootCmd) speedtest() *serpent.Command { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") opts.BlockEndpoints = true } + if r.noNetworkTelemetry { + opts.EnableTelemetry = true + } if pcapFile != "" { s := capture.New() opts.CaptureHook = s.LogPacket diff --git a/cli/ssh.go b/cli/ssh.go index e4e9fadf5e8e8..050c7d7fe48c3 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -243,8 +243,9 @@ func (r *RootCmd) ssh() *serpent.Command { } conn, err := workspacesdk.New(client). DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ - Logger: logger, - BlockEndpoints: r.disableDirect, + Logger: logger, + BlockEndpoints: r.disableDirect, + EnableTelemetry: !r.noNetworkTelemetry, }) if err != nil { return xerrors.Errorf("dial agent: %w", err) diff --git a/cli/testdata/coder_--help.golden b/cli/testdata/coder_--help.golden index e970347890eb2..294010e7678e6 100644 --- a/cli/testdata/coder_--help.golden +++ b/cli/testdata/coder_--help.golden @@ -66,6 +66,9 @@ variables or flags. --disable-direct-connections bool, $CODER_DISABLE_DIRECT_CONNECTIONS Disable direct (P2P) connections to workspaces. + --disable-network-telemetry bool, $CODER_DISABLE_NETWORK_TELEMETRY + Disable network telemetry. + --global-config string, $CODER_CONFIG_DIR (default: ~/.config/coderv2) Path to the global `coder` config directory. diff --git a/codersdk/workspacesdk/connector.go b/codersdk/workspacesdk/connector.go index 5ac009af15091..9c65fbd48678c 100644 --- a/codersdk/workspacesdk/connector.go +++ b/codersdk/workspacesdk/connector.go @@ -58,32 +58,28 @@ type tailnetAPIConnector struct { coordinateURL string dialOptions *websocket.DialOptions conn tailnetConn + customDialFn func() (proto.DRPCTailnetClient, error) + + clientMu sync.RWMutex + client proto.DRPCTailnetClient connected chan error isFirst bool closed chan struct{} } -// runTailnetAPIConnector creates and runs a tailnetAPIConnector -func runTailnetAPIConnector( - ctx context.Context, logger slog.Logger, - agentID uuid.UUID, coordinateURL string, dialOptions *websocket.DialOptions, - conn tailnetConn, -) *tailnetAPIConnector { - tac := &tailnetAPIConnector{ +// Create a new tailnetAPIConnector without running it +func newTailnetAPIConnector(ctx context.Context, logger slog.Logger, agentID uuid.UUID, coordinateURL string, dialOptions *websocket.DialOptions) *tailnetAPIConnector { + return &tailnetAPIConnector{ ctx: ctx, logger: logger, agentID: agentID, coordinateURL: coordinateURL, dialOptions: dialOptions, - conn: conn, + conn: nil, connected: make(chan error, 1), closed: make(chan struct{}), } - tac.gracefulCtx, tac.cancelGracefulCtx = context.WithCancel(context.Background()) - go tac.manageGracefulTimeout() - go tac.run() - return tac } // manageGracefulTimeout allows the gracefulContext to last 1 second longer than the main context @@ -99,21 +95,27 @@ func (tac *tailnetAPIConnector) manageGracefulTimeout() { } } -func (tac *tailnetAPIConnector) run() { - tac.isFirst = true - defer close(tac.closed) - for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(tac.ctx); { - tailnetClient, err := tac.dial() - if xerrors.Is(err, &codersdk.Error{}) { - return - } - if err != nil { - continue +// Runs a tailnetAPIConnector using the provided connection +func (tac *tailnetAPIConnector) runConnector(conn tailnetConn) { + tac.conn = conn + tac.gracefulCtx, tac.cancelGracefulCtx = context.WithCancel(context.Background()) + go tac.manageGracefulTimeout() + go func() { + tac.isFirst = true + defer close(tac.closed) + for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(tac.ctx); { + tailnetClient, err := tac.dial() + if err != nil { + continue + } + tac.clientMu.Lock() + tac.client = tailnetClient + tac.clientMu.Unlock() + tac.logger.Debug(tac.ctx, "obtained tailnet API v2+ client") + tac.coordinateAndDERPMap(tailnetClient) + tac.logger.Debug(tac.ctx, "tailnet API v2+ connection lost") } - tac.logger.Debug(tac.ctx, "obtained tailnet API v2+ client") - tac.coordinateAndDERPMap(tailnetClient) - tac.logger.Debug(tac.ctx, "tailnet API v2+ connection lost") - } + }() } var permanentErrorStatuses = []int{ @@ -123,6 +125,10 @@ var permanentErrorStatuses = []int{ } func (tac *tailnetAPIConnector) dial() (proto.DRPCTailnetClient, error) { + if tac.customDialFn != nil { + return tac.customDialFn() + } + tac.logger.Debug(tac.ctx, "dialing Coder tailnet v2+ API") // nolint:bodyclose ws, res, err := websocket.Dial(tac.ctx, tac.coordinateURL, tac.dialOptions) @@ -194,7 +200,10 @@ func (tac *tailnetAPIConnector) coordinateAndDERPMap(client proto.DRPCTailnetCli // we do NOT want to gracefully disconnect on the coordinate() routine. So, we'll just // close the underlying connection. This will trigger a retry of the control plane in // run(). + tac.clientMu.Lock() client.DRPCConn().Close() + tac.client = nil + tac.clientMu.Unlock() // Note that derpMap() logs it own errors, we don't bother here. } }() @@ -258,3 +267,18 @@ func (tac *tailnetAPIConnector) derpMap(client proto.DRPCTailnetClient) error { tac.conn.SetDERPMap(dm) } } + +func (tac *tailnetAPIConnector) SendTelemetryEvent(event *proto.TelemetryEvent) { + tac.clientMu.RLock() + // We hold the lock for the entire telemetry request, but this would only block + // a coordinate retry, and closing the connection. + defer tac.clientMu.RUnlock() + if tac.client == nil { + return + } + ctx, cancel := context.WithTimeout(tac.ctx, 5*time.Second) + defer cancel() + _, _ = tac.client.PostTelemetry(ctx, &proto.TelemetryRequest{ + Events: []*proto.TelemetryEvent{event}, + }) +} diff --git a/codersdk/workspacesdk/connector_internal_test.go b/codersdk/workspacesdk/connector_internal_test.go index 2e5716ee17870..70bbbc25d141b 100644 --- a/codersdk/workspacesdk/connector_internal_test.go +++ b/codersdk/workspacesdk/connector_internal_test.go @@ -75,7 +75,8 @@ func TestTailnetAPIConnector_Disconnects(t *testing.T) { fConn := newFakeTailnetConn() - uut := runTailnetAPIConnector(ctx, logger, agentID, svr.URL, &websocket.DialOptions{}, fConn) + uut := newTailnetAPIConnector(ctx, logger, agentID, svr.URL, &websocket.DialOptions{}) + uut.runConnector(fConn) call := testutil.RequireRecvCtx(ctx, t, fCoord.CoordinateCalls) reqTun := testutil.RequireRecvCtx(ctx, t, call.Reqs) diff --git a/codersdk/workspacesdk/workspacesdk.go b/codersdk/workspacesdk/workspacesdk.go index 04765c13d9877..ca4d8add730cc 100644 --- a/codersdk/workspacesdk/workspacesdk.go +++ b/codersdk/workspacesdk/workspacesdk.go @@ -181,6 +181,9 @@ type DialAgentOptions struct { // CaptureHook is a callback that captures Disco packets and packets sent // into the tailnet tunnel. CaptureHook capture.Callback + // Whether the client will send network telemetry events + // Enable instead of Disable so it's initialized to false + EnableTelemetry bool } func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options *DialAgentOptions) (agentConn *AgentConn, err error) { @@ -196,29 +199,6 @@ func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options * options.BlockEndpoints = true } - ip := tailnet.IP() - var header http.Header - if headerTransport, ok := c.client.HTTPClient.Transport.(*codersdk.HeaderTransport); ok { - header = headerTransport.Header - } - conn, err := tailnet.NewConn(&tailnet.Options{ - Addresses: []netip.Prefix{netip.PrefixFrom(ip, 128)}, - DERPMap: connInfo.DERPMap, - DERPHeader: &header, - DERPForceWebSockets: connInfo.DERPForceWebSockets, - Logger: options.Logger, - BlockEndpoints: c.client.DisableDirectConnections || options.BlockEndpoints, - CaptureHook: options.CaptureHook, - }) - if err != nil { - return nil, xerrors.Errorf("create tailnet: %w", err) - } - defer func() { - if err != nil { - _ = conn.Close() - } - }() - headers := make(http.Header) tokenHeader := codersdk.SessionTokenHeader if c.client.SessionTokenHeader != "" { @@ -251,16 +231,43 @@ func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options * q.Add("version", "2.0") coordinateURL.RawQuery = q.Encode() - connector := runTailnetAPIConnector(ctx, options.Logger, - agentID, coordinateURL.String(), + connector := newTailnetAPIConnector(ctx, options.Logger, agentID, coordinateURL.String(), &websocket.DialOptions{ HTTPClient: c.client.HTTPClient, HTTPHeader: headers, // Need to disable compression to avoid a data-race. CompressionMode: websocket.CompressionDisabled, - }, - conn, - ) + }) + + ip := tailnet.IP() + var header http.Header + if headerTransport, ok := c.client.HTTPClient.Transport.(*codersdk.HeaderTransport); ok { + header = headerTransport.Header + } + var telemetrySink tailnet.TelemetrySink + if options.EnableTelemetry { + telemetrySink = connector + } + conn, err := tailnet.NewConn(&tailnet.Options{ + Addresses: []netip.Prefix{netip.PrefixFrom(ip, 128)}, + DERPMap: connInfo.DERPMap, + DERPHeader: &header, + DERPForceWebSockets: connInfo.DERPForceWebSockets, + Logger: options.Logger, + BlockEndpoints: c.client.DisableDirectConnections || options.BlockEndpoints, + CaptureHook: options.CaptureHook, + TelemetrySink: telemetrySink, + }) + if err != nil { + return nil, xerrors.Errorf("create tailnet: %w", err) + } + defer func() { + if err != nil { + _ = conn.Close() + } + }() + connector.runConnector(conn) + options.Logger.Debug(ctx, "running tailnet API v2+ connector") select { diff --git a/docs/cli.md b/docs/cli.md index 70dd29e28b9da..2962425499040 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -149,6 +149,15 @@ Enable verbose output. Disable direct (P2P) connections to workspaces. +### --disable-network-telemetry + +| | | +| ----------- | --------------------------------------------- | +| Type | bool | +| Environment | $CODER_DISABLE_NETWORK_TELEMETRY | + +Disable network telemetry. + ### --global-config | | | diff --git a/enterprise/cli/testdata/coder_--help.golden b/enterprise/cli/testdata/coder_--help.golden index 7c2ff5c835dff..6895cdde3c9f4 100644 --- a/enterprise/cli/testdata/coder_--help.golden +++ b/enterprise/cli/testdata/coder_--help.golden @@ -30,6 +30,9 @@ variables or flags. --disable-direct-connections bool, $CODER_DISABLE_DIRECT_CONNECTIONS Disable direct (P2P) connections to workspaces. + --disable-network-telemetry bool, $CODER_DISABLE_NETWORK_TELEMETRY + Disable network telemetry. + --global-config string, $CODER_CONFIG_DIR (default: ~/.config/coderv2) Path to the global `coder` config directory. diff --git a/tailnet/conn.go b/tailnet/conn.go index 8b82c455e4788..3a2843539ed04 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -99,6 +99,15 @@ type Options struct { // ForceNetworkUp forces the network to be considered up. magicsock will not // do anything if it thinks it can't reach the internet. ForceNetworkUp bool + // TelemetrySink is optional. + TelemetrySink TelemetrySink +} + +// TelemetrySink allows tailnet.Conn to send network telemetry to the Coder +// server. +type TelemetrySink interface { + // SendTelemetryEvent sends a telemetry event to some external sink. + SendTelemetryEvent(event *proto.TelemetryEvent) } // NodeID creates a Tailscale NodeID from the last 8 bytes of a UUID. It ensures @@ -113,6 +122,13 @@ func NodeID(uid uuid.UUID) tailcfg.NodeID { return tailcfg.NodeID(id) } +func (c *Conn) sendTelemetryEvent(e *proto.TelemetryEvent) { + if c.telemetrySink == nil { + return + } + c.telemetrySink.SendTelemetryEvent(e) +} + // NewConn constructs a new Wireguard server that will accept connections from the addresses provided. func NewConn(options *Options) (conn *Conn, err error) { if options == nil { @@ -259,6 +275,7 @@ func NewConn(options *Options) (conn *Conn, err error) { wireguardEngine: wireguardEngine, configMaps: cfgMaps, nodeUpdater: nodeUp, + telemetrySink: options.TelemetrySink, } defer func() { if err != nil { @@ -316,6 +333,7 @@ type Conn struct { wireguardRouter *router.Config wireguardEngine wgengine.Engine listeners map[listenKey]*listener + telemetrySink TelemetrySink trafficStats *connstats.Statistics } diff --git a/tailnet/logger.go b/tailnet/logger.go new file mode 100644 index 0000000000000..044fcde0a9ca3 --- /dev/null +++ b/tailnet/logger.go @@ -0,0 +1,200 @@ +package tailnet + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "io" + "net/netip" + "regexp" + "strings" + "sync" + + "golang.org/x/xerrors" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/sloghuman" + "github.com/coder/coder/v2/cryptorand" + "github.com/coder/coder/v2/tailnet/proto" +) + +var ipv4And6Regex = regexp.MustCompile(`(((::ffff:)?(25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}|([a-f0-9:]+:+)+[a-f0-9]+)`) + +// Used to store a number of slog logger, and a logger sink for creating network telemetry events +type multiLogger struct { + loggers []slog.Logger +} + +func newMultiLogger(loggers ...slog.Logger) multiLogger { + return multiLogger{loggers: loggers} +} + +func (m multiLogger) appendLogger(logger slog.Logger) multiLogger { + return multiLogger{loggers: append(m.loggers, logger)} +} + +func (m multiLogger) Critical(ctx context.Context, msg string, fields ...any) { + for _, i := range m.loggers { + i.Critical(ctx, msg, fields...) + } +} + +func (m multiLogger) Debug(ctx context.Context, msg string, fields ...any) { + for _, i := range m.loggers { + i.Debug(ctx, msg, fields...) + } +} + +func (m multiLogger) Error(ctx context.Context, msg string, fields ...any) { + for _, i := range m.loggers { + i.Error(ctx, msg, fields...) + } +} + +func (m multiLogger) Fatal(ctx context.Context, msg string, fields ...any) { + for _, i := range m.loggers { + i.Fatal(ctx, msg, fields...) + } +} + +func (m multiLogger) Info(ctx context.Context, msg string, fields ...any) { + for _, i := range m.loggers { + i.Info(ctx, msg, fields...) + } +} + +func (m multiLogger) Warn(ctx context.Context, msg string, fields ...any) { + for _, i := range m.loggers { + i.Warn(ctx, msg, fields...) + } +} + +func (m multiLogger) Named(name string) multiLogger { + var loggers []slog.Logger + for _, i := range m.loggers { + loggers = append(loggers, i.Named(name)) + } + return multiLogger{loggers: loggers} +} + +func (m multiLogger) With(fields ...slog.Field) multiLogger { + var loggers []slog.Logger + for _, i := range m.loggers { + loggers = append(loggers, i.With(fields...)) + } + return multiLogger{loggers: loggers} +} + +// A logger sink that extracts (anonymized) IP addresses from logs for building +// network telemetry events +type bufferLogSink struct { + sink slog.Sink + mu sync.Mutex + logs []string + // We use the same salt so the same IP hashes to the same value. + hashSalt string + // A cache to avoid hashing the same IP multiple times. + ipToHash map[string]string + hashedIPs map[string]*proto.IPFields +} + +var _ slog.Sink = &bufferLogSink{} + +var _ io.Writer = &bufferLogSink{} + +func newBufferLogSink() (*bufferLogSink, error) { + hashSalt, err := cryptorand.String(16) + if err != nil { + return nil, err + } + out := &bufferLogSink{ + logs: []string{}, + hashSalt: hashSalt, + ipToHash: make(map[string]string), + hashedIPs: make(map[string]*proto.IPFields), + } + out.sink = sloghuman.Sink(out) + return out, nil +} + +func (b *bufferLogSink) getLogs() ([]string, map[string]*proto.IPFields) { + b.mu.Lock() + defer b.mu.Unlock() + return append([]string{}, b.logs...), b.hashedIPs +} + +// Write implements io.Writer. +func (b *bufferLogSink) Write(p []byte) (n int, err error) { + b.mu.Lock() + defer b.mu.Unlock() + + // sloghuman writes a full log line in a single Write call with a trailing + // newline. + logLine := strings.TrimSuffix(string(p), "\n") + + logLineSplit := strings.SplitN(logLine, "]", 2) + logLineAfterLevel := logLine + if len(logLineAfterLevel) == 2 { + logLineAfterLevel = logLineSplit[1] + } + for _, match := range ipv4And6Regex.FindAllString(logLineAfterLevel, -1) { + hash, err := b.processIPLocked(match) + if err == nil { + logLine = strings.ReplaceAll(logLine, match, hash) + } + } + + b.logs = append(b.logs, logLine) + return len(p), nil +} + +// LogEntry implements slog.Sink. +func (b *bufferLogSink) LogEntry(ctx context.Context, e slog.SinkEntry) { + // This will call (*bufferLogSink).Write + b.sink.LogEntry(ctx, e) +} + +// Sync implements slog.Sink. +func (b *bufferLogSink) Sync() { + b.sink.Sync() +} + +// processIPLocked will look up the IP in the cache, or hash and salt it and add +// to the cache. It will also add it to hashedIPs. +// +// b.mu must be held. +func (b *bufferLogSink) processIPLocked(ip string) (string, error) { + if hashStr, ok := b.ipToHash[ip]; ok { + return hashStr, nil + } + + addr, err := netip.ParseAddr(ip) + if err != nil { + return "", xerrors.Errorf("failed to parse IP %q: %w", ip, err) + } + version := int32(4) + if addr.Is6() { + version = 6 + } + + class := proto.IPFields_PUBLIC + switch { + case addr.IsLoopback(): + class = proto.IPFields_LOOPBACK + case addr.IsLinkLocalUnicast(): + class = proto.IPFields_LINK_LOCAL + case addr.IsLinkLocalMulticast(): + class = proto.IPFields_LINK_LOCAL + case addr.IsPrivate(): + class = proto.IPFields_PRIVATE + } + + hash := sha256.Sum256([]byte(b.hashSalt + ip)) + hashStr := hex.EncodeToString(hash[:]) + b.ipToHash[ip] = hashStr + b.hashedIPs[hashStr] = &proto.IPFields{ + Version: version, + Class: class, + } + return hashStr, nil +} diff --git a/tailnet/logger_internal_test.go b/tailnet/logger_internal_test.go new file mode 100644 index 0000000000000..7b0b253c39672 --- /dev/null +++ b/tailnet/logger_internal_test.go @@ -0,0 +1,145 @@ +package tailnet + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/tailnet/proto" + + "cdr.dev/slog" +) + +func TestBufferLogSink(t *testing.T) { + t.Parallel() + + t.Run("NoIP", func(t *testing.T) { + t.Parallel() + ctx := context.Background() + sink, err := newBufferLogSink() + require.NoError(t, err) + logger := slog.Make(sink).Leveled(slog.LevelDebug) + + logger.Debug(ctx, "line1") + logger.Debug(ctx, "line2 fe80") + logger.Debug(ctx, "line3 xxxx::x") + + logs, hashes := sink.getLogs() + require.Len(t, logs, 3) + require.Len(t, hashes, 0) + require.Contains(t, logs[0], "line1") + require.Contains(t, logs[1], "line2 fe80") + require.Contains(t, logs[2], "line3 xxxx::x") + }) + + t.Run("OneOrMoreIPs", func(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + ip string + expectedVersion int32 + expectedClass proto.IPFields_IPClass + }{ + { + name: "IPv4/Public", + ip: "142.250.71.78", + expectedVersion: 4, + expectedClass: proto.IPFields_PUBLIC, + }, + { + name: "IPv4/Private", + ip: "192.168.0.1", + expectedVersion: 4, + expectedClass: proto.IPFields_PRIVATE, + }, + { + name: "IPv4/LinkLocal", + ip: "169.254.1.1", + expectedVersion: 4, + expectedClass: proto.IPFields_LINK_LOCAL, + }, + { + name: "IPv4/Loopback", + ip: "127.0.0.1", + expectedVersion: 4, + expectedClass: proto.IPFields_LOOPBACK, + }, + { + name: "IPv6/Public", + ip: "2404:6800:4006:812::200e", + expectedVersion: 6, + expectedClass: proto.IPFields_PUBLIC, + }, + { + name: "IPv6/Private", + ip: "fd12:3456:789a:1::1", + expectedVersion: 6, + expectedClass: proto.IPFields_PRIVATE, + }, + { + name: "IPv6/LinkLocal", + ip: "fe80::1", + expectedVersion: 6, + expectedClass: proto.IPFields_LINK_LOCAL, + }, + { + name: "IPv6/Loopback", + ip: "::1", + expectedVersion: 6, + expectedClass: proto.IPFields_LOOPBACK, + }, + { + name: "IPv6/IPv4Mapped", + ip: "::ffff:1.2.3.4", + expectedVersion: 6, + expectedClass: proto.IPFields_PUBLIC, + }, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + sink, err := newBufferLogSink() + require.NoError(t, err) + logger := slog.Make(sink).Leveled(slog.LevelDebug) + + ipWithPort := c.ip + ":8080" + if c.expectedVersion == 6 { + ipWithPort = fmt.Sprintf("[%s]:8080", c.ip) + } + + logger.Debug(ctx, "line1", slog.F("ip", c.ip)) + logger.Debug(ctx, fmt.Sprintf("line2: %s/24", c.ip)) + logger.Debug(ctx, fmt.Sprintf("line3: %s foo (%s)", ipWithPort, c.ip)) + + logs, hashes := sink.getLogs() + require.Len(t, logs, 3) + require.Len(t, hashes, 1) + for _, log := range logs { + t.Log(log) + } + + // This only runs once since we only processed a single IP. + for expectedHash, ipFields := range hashes { + hashedIPWithPort := expectedHash + ":8080" + if c.expectedVersion == 6 { + hashedIPWithPort = fmt.Sprintf("[%s]:8080", expectedHash) + } + + require.Contains(t, logs[0], "line1") + require.Contains(t, logs[0], "ip="+expectedHash) + require.Contains(t, logs[1], fmt.Sprintf("line2: %s/24", expectedHash)) + require.Contains(t, logs[2], fmt.Sprintf("line3: %s foo (%s)", hashedIPWithPort, expectedHash)) + + require.Equal(t, c.expectedVersion, ipFields.Version) + require.Equal(t, c.expectedClass, ipFields.Class) + } + }) + } + }) +} From 0718987f971ac906cad993ad29968d8802b78c0b Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 26 Jun 2024 15:43:41 +0000 Subject: [PATCH 02/19] fixup --- codersdk/workspacesdk/connector.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/codersdk/workspacesdk/connector.go b/codersdk/workspacesdk/connector.go index 9c65fbd48678c..dc4bebd147e09 100644 --- a/codersdk/workspacesdk/connector.go +++ b/codersdk/workspacesdk/connector.go @@ -38,6 +38,7 @@ type tailnetConn interface { // // 1) run the Coordinate API and pass node information back and forth // 2) stream DERPMap updates and program the Conn +// 3) Send network telemetry events // // These functions share the same websocket, and so are combined here so that if we hit a problem // we tear the whole thing down and start over with a new websocket. @@ -58,7 +59,6 @@ type tailnetAPIConnector struct { coordinateURL string dialOptions *websocket.DialOptions conn tailnetConn - customDialFn func() (proto.DRPCTailnetClient, error) clientMu sync.RWMutex client proto.DRPCTailnetClient @@ -125,10 +125,6 @@ var permanentErrorStatuses = []int{ } func (tac *tailnetAPIConnector) dial() (proto.DRPCTailnetClient, error) { - if tac.customDialFn != nil { - return tac.customDialFn() - } - tac.logger.Debug(tac.ctx, "dialing Coder tailnet v2+ API") // nolint:bodyclose ws, res, err := websocket.Dial(tac.ctx, tac.coordinateURL, tac.dialOptions) From 80181ad74890379acfc662cc3d7e0571e398bcf4 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 26 Jun 2024 15:49:34 +0000 Subject: [PATCH 03/19] fixup --- cli/ping.go | 2 +- cli/portforward.go | 2 +- cli/speedtest.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/ping.go b/cli/ping.go index ff6c318cc1b84..4d4e9308909dc 100644 --- a/cli/ping.go +++ b/cli/ping.go @@ -58,7 +58,7 @@ func (r *RootCmd) ping() *serpent.Command { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") opts.BlockEndpoints = true } - if r.noNetworkTelemetry { + if !r.noNetworkTelemetry { opts.EnableTelemetry = true } conn, err := workspacesdk.New(client).DialAgent(ctx, workspaceAgent.ID, opts) diff --git a/cli/portforward.go b/cli/portforward.go index bef0bb6f12be1..2a0126032e3bb 100644 --- a/cli/portforward.go +++ b/cli/portforward.go @@ -106,7 +106,7 @@ func (r *RootCmd) portForward() *serpent.Command { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") opts.BlockEndpoints = true } - if r.noNetworkTelemetry { + if !r.noNetworkTelemetry { opts.EnableTelemetry = true } conn, err := workspacesdk.New(client).DialAgent(ctx, workspaceAgent.ID, opts) diff --git a/cli/speedtest.go b/cli/speedtest.go index 3f34e5796f66f..9f23d0c58f780 100644 --- a/cli/speedtest.go +++ b/cli/speedtest.go @@ -102,7 +102,7 @@ func (r *RootCmd) speedtest() *serpent.Command { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") opts.BlockEndpoints = true } - if r.noNetworkTelemetry { + if !r.noNetworkTelemetry { opts.EnableTelemetry = true } if pcapFile != "" { From 3b7f5b20f32240c2afff4192e7afaa60c41aaef8 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Thu, 27 Jun 2024 15:01:37 +0000 Subject: [PATCH 04/19] wip log sink --- codersdk/workspacesdk/connector.go | 16 +- .../workspacesdk/connector_internal_test.go | 3 +- tailnet/configmaps.go | 4 +- tailnet/configmaps_internal_test.go | 36 +- tailnet/conn.go | 120 +++++- tailnet/logger.go | 1 + tailnet/logger_internal_test.go | 3 +- tailnet/node.go | 4 +- tailnet/node_internal_test.go | 30 +- tailnet/proto/tailnet.pb.go | 408 +++++++++--------- tailnet/proto/tailnet.proto | 3 +- 11 files changed, 357 insertions(+), 271 deletions(-) diff --git a/codersdk/workspacesdk/connector.go b/codersdk/workspacesdk/connector.go index dc4bebd147e09..b3c23350740a2 100644 --- a/codersdk/workspacesdk/connector.go +++ b/codersdk/workspacesdk/connector.go @@ -7,12 +7,16 @@ import ( "io" "net/http" "slices" + "strings" "sync" + "sync/atomic" "time" "github.com/google/uuid" "golang.org/x/xerrors" "nhooyr.io/websocket" + "storj.io/drpc" + "storj.io/drpc/drpcerr" "tailscale.com/tailcfg" "cdr.dev/slog" @@ -66,6 +70,10 @@ type tailnetAPIConnector struct { connected chan error isFirst bool closed chan struct{} + + // Set to true if we get a response from the server that it doesn't support + // network telemetry. + telemetryDisabled atomic.Bool } // Create a new tailnetAPIConnector without running it @@ -269,12 +277,16 @@ func (tac *tailnetAPIConnector) SendTelemetryEvent(event *proto.TelemetryEvent) // We hold the lock for the entire telemetry request, but this would only block // a coordinate retry, and closing the connection. defer tac.clientMu.RUnlock() - if tac.client == nil { + if tac.client == nil || tac.telemetryDisabled.Load() { return } ctx, cancel := context.WithTimeout(tac.ctx, 5*time.Second) defer cancel() - _, _ = tac.client.PostTelemetry(ctx, &proto.TelemetryRequest{ + _, err := tac.client.PostTelemetry(ctx, &proto.TelemetryRequest{ Events: []*proto.TelemetryEvent{event}, }) + if drpcerr.Code(err) == drpcerr.Unimplemented || drpc.ProtocolError.Has(err) && strings.Contains(err.Error(), "unknown rpc: ") { + tac.logger.Debug(tac.ctx, "attempted to send telemetry to a server that doesn't support it", slog.Error(err)) + tac.telemetryDisabled.Store(true) + } } diff --git a/codersdk/workspacesdk/connector_internal_test.go b/codersdk/workspacesdk/connector_internal_test.go index 70bbbc25d141b..845be132163d2 100644 --- a/codersdk/workspacesdk/connector_internal_test.go +++ b/codersdk/workspacesdk/connector_internal_test.go @@ -128,7 +128,8 @@ func TestTailnetAPIConnector_UplevelVersion(t *testing.T) { fConn := newFakeTailnetConn() - uut := runTailnetAPIConnector(ctx, logger, agentID, svr.URL, &websocket.DialOptions{}, fConn) + uut := newTailnetAPIConnector(ctx, logger, agentID, svr.URL, &websocket.DialOptions{}) + uut.runConnector(fConn) err := testutil.RequireRecvCtx(ctx, t, uut.connected) var sdkErr *codersdk.Error diff --git a/tailnet/configmaps.go b/tailnet/configmaps.go index e6258817afaa7..2f835a2875853 100644 --- a/tailnet/configmaps.go +++ b/tailnet/configmaps.go @@ -66,14 +66,14 @@ type configMaps struct { peers map[uuid.UUID]*peerLifecycle addresses []netip.Prefix derpMap *tailcfg.DERPMap - logger slog.Logger + logger multiLogger blockEndpoints bool // for testing clock clock.Clock } -func newConfigMaps(logger slog.Logger, engine engineConfigurable, nodeID tailcfg.NodeID, nodeKey key.NodePrivate, discoKey key.DiscoPublic) *configMaps { +func newConfigMaps(logger multiLogger, engine engineConfigurable, nodeID tailcfg.NodeID, nodeKey key.NodePrivate, discoKey key.DiscoPublic) *configMaps { pubKey := nodeKey.Public() c := &configMaps{ phased: phased{Cond: *(sync.NewCond(&sync.Mutex{}))}, diff --git a/tailnet/configmaps_internal_test.go b/tailnet/configmaps_internal_test.go index 83b15387a9a43..dc522789f5765 100644 --- a/tailnet/configmaps_internal_test.go +++ b/tailnet/configmaps_internal_test.go @@ -29,7 +29,7 @@ import ( func TestConfigMaps_setAddresses_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -87,7 +87,7 @@ func TestConfigMaps_setAddresses_different(t *testing.T) { func TestConfigMaps_setAddresses_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -118,7 +118,7 @@ func TestConfigMaps_setAddresses_same(t *testing.T) { func TestConfigMaps_updatePeers_new(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -188,7 +188,7 @@ func TestConfigMaps_updatePeers_new(t *testing.T) { func TestConfigMaps_updatePeers_new_waitForHandshake_neverConfigures(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -232,7 +232,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_neverConfigures(t *testing. func TestConfigMaps_updatePeers_new_waitForHandshake_outOfOrder(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -303,7 +303,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_outOfOrder(t *testing.T) { func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -374,7 +374,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) { func TestConfigMaps_updatePeers_new_waitForHandshake_timeout(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -432,7 +432,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_timeout(t *testing.T) { func TestConfigMaps_updatePeers_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -491,7 +491,7 @@ func TestConfigMaps_updatePeers_same(t *testing.T) { func TestConfigMaps_updatePeers_disconnect(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -559,7 +559,7 @@ func TestConfigMaps_updatePeers_disconnect(t *testing.T) { func TestConfigMaps_updatePeers_lost(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -644,7 +644,7 @@ func TestConfigMaps_updatePeers_lost(t *testing.T) { func TestConfigMaps_updatePeers_lost_and_found(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -729,7 +729,7 @@ func TestConfigMaps_updatePeers_lost_and_found(t *testing.T) { func TestConfigMaps_setAllPeersLost(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -815,7 +815,7 @@ func TestConfigMaps_setAllPeersLost(t *testing.T) { func TestConfigMaps_setBlockEndpoints_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -859,7 +859,7 @@ func TestConfigMaps_setBlockEndpoints_different(t *testing.T) { func TestConfigMaps_setBlockEndpoints_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -902,7 +902,7 @@ func TestConfigMaps_setBlockEndpoints_same(t *testing.T) { func TestConfigMaps_setDERPMap_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -943,7 +943,7 @@ func TestConfigMaps_setDERPMap_different(t *testing.T) { func TestConfigMaps_setDERPMap_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -1012,7 +1012,7 @@ func TestConfigMaps_setDERPMap_same(t *testing.T) { func TestConfigMaps_fillPeerDiagnostics(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -1120,7 +1120,7 @@ func TestConfigMaps_updatePeers_nonexist(t *testing.T) { t.Run(k.String(), func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) diff --git a/tailnet/conn.go b/tailnet/conn.go index 3a2843539ed04..fdbbbc4ffc11f 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -15,6 +15,9 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/google/uuid" "golang.org/x/xerrors" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "tailscale.com/envknob" @@ -99,6 +102,8 @@ type Options struct { // ForceNetworkUp forces the network to be considered up. magicsock will not // do anything if it thinks it can't reach the internet. ForceNetworkUp bool + // Network Telemetry Client Type: CLI | Agent | coderd + ClientType proto.TelemetryEvent_ClientType // TelemetrySink is optional. TelemetrySink TelemetrySink } @@ -122,13 +127,6 @@ func NodeID(uid uuid.UUID) tailcfg.NodeID { return tailcfg.NodeID(id) } -func (c *Conn) sendTelemetryEvent(e *proto.TelemetryEvent) { - if c.telemetrySink == nil { - return - } - c.telemetrySink.SendTelemetryEvent(e) -} - // NewConn constructs a new Wireguard server that will accept connections from the addresses provided. func NewConn(options *Options) (conn *Conn, err error) { if options == nil { @@ -138,6 +136,19 @@ func NewConn(options *Options) (conn *Conn, err error) { return nil, xerrors.New("At least one IP range must be provided") } + var ( + logger = newMultiLogger(options.Logger) + telemetryLogSink *bufferLogSink + ) + if options.TelemetrySink != nil { + var err error + telemetryLogSink, err = newBufferLogSink() + if err != nil { + return nil, xerrors.Errorf("create telemetry log sink: %w", err) + } + logger = logger.appendLogger(slog.Make(telemetryLogSink).Leveled(slog.LevelDebug)) + } + nodePrivateKey := key.NewNode() var nodeID tailcfg.NodeID @@ -152,7 +163,7 @@ func NewConn(options *Options) (conn *Conn, err error) { nodeID = tailcfg.NodeID(uid) } - wireguardMonitor, err := netmon.New(Logger(options.Logger.Named("net.wgmonitor"))) + wireguardMonitor, err := netmon.New(Logger(logger.Named("net.wgmonitor"))) if err != nil { return nil, xerrors.Errorf("create wireguard link monitor: %w", err) } @@ -163,10 +174,10 @@ func NewConn(options *Options) (conn *Conn, err error) { }() dialer := &tsdial.Dialer{ - Logf: Logger(options.Logger.Named("net.tsdial")), + Logf: Logger(logger.Named("net.tsdial")), } sys := new(tsd.System) - wireguardEngine, err := wgengine.NewUserspaceEngine(Logger(options.Logger.Named("net.wgengine")), wgengine.Config{ + wireguardEngine, err := wgengine.NewUserspaceEngine(Logger(logger.Named("net.wgengine")), wgengine.Config{ NetMon: wireguardMonitor, Dialer: dialer, ListenPort: options.ListenPort, @@ -201,13 +212,13 @@ func NewConn(options *Options) (conn *Conn, err error) { if v, ok := os.LookupEnv(EnvMagicsockDebugLogging); ok { vBool, err := strconv.ParseBool(v) if err != nil { - options.Logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging disabled due to invalid value %s=%q, use true or false", EnvMagicsockDebugLogging, v)) + logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging disabled due to invalid value %s=%q, use true or false", EnvMagicsockDebugLogging, v)) } else { magicConn.SetDebugLoggingEnabled(vBool) - options.Logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging set by %s=%t", EnvMagicsockDebugLogging, vBool)) + logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging set by %s=%t", EnvMagicsockDebugLogging, vBool)) } } else { - options.Logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging disabled, use %s=true to enable", EnvMagicsockDebugLogging)) + logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging disabled, use %s=true to enable", EnvMagicsockDebugLogging)) } // Update the keys for the magic connection! @@ -217,7 +228,7 @@ func NewConn(options *Options) (conn *Conn, err error) { } netStack, err := netstack.Create( - Logger(options.Logger.Named("net.netstack")), + Logger(logger.Named("net.netstack")), sys.Tun.Get(), wireguardEngine, magicConn, @@ -235,7 +246,7 @@ func NewConn(options *Options) (conn *Conn, err error) { wireguardEngine = wgengine.NewWatchdog(wireguardEngine) cfgMaps := newConfigMaps( - options.Logger, + logger, wireguardEngine, nodeID, nodePrivateKey, @@ -248,7 +259,7 @@ func NewConn(options *Options) (conn *Conn, err error) { cfgMaps.setBlockEndpoints(options.BlockEndpoints) nodeUp := newNodeUpdater( - options.Logger, + logger, nil, nodeID, nodePrivateKey.Public(), @@ -261,8 +272,9 @@ func NewConn(options *Options) (conn *Conn, err error) { magicConn.SetDERPForcedWebsocketCallback(nodeUp.setDERPForcedWebsocket) server := &Conn{ + id: uuid.New(), closed: make(chan struct{}), - logger: options.Logger, + logger: logger, magicConn: magicConn, dialer: dialer, listeners: map[listenKey]*listener{}, @@ -276,6 +288,7 @@ func NewConn(options *Options) (conn *Conn, err error) { configMaps: cfgMaps, nodeUpdater: nodeUp, telemetrySink: options.TelemetrySink, + telemetryLogs: telemetryLogSink, } defer func() { if err != nil { @@ -319,9 +332,11 @@ func IPFromUUID(uid uuid.UUID) netip.Addr { // Conn is an actively listening Wireguard connection. type Conn struct { + // ID must be unique to this connection + id uuid.UUID mutex sync.Mutex closed chan struct{} - logger slog.Logger + logger multiLogger dialer *tsdial.Dialer tunDevice *tstun.Wrapper @@ -333,7 +348,12 @@ type Conn struct { wireguardRouter *router.Config wireguardEngine wgengine.Engine listeners map[listenKey]*listener - telemetrySink TelemetrySink + clientType proto.TelemetryEvent_ClientType + + telemetrySink TelemetrySink + // telemetryLogs will be nil if telemetrySink is nil. + telemetryLogs *bufferLogSink + telemetryWg sync.WaitGroup trafficStats *connstats.Statistics } @@ -492,6 +512,7 @@ func (c *Conn) AwaitReachable(ctx context.Context, ip netip.Addr) bool { for { select { case <-completedCtx.Done(): + _ = c.connectedTelemetryEvent() return true case <-t.C: // Pings can take a while, so we can run multiple @@ -512,6 +533,7 @@ func (c *Conn) Closed() <-chan struct{} { // Close shuts down the Wireguard connection. func (c *Conn) Close() error { c.logger.Info(context.Background(), "closing tailnet Conn") + c.telemetryWg.Wait() c.configMaps.close() c.nodeUpdater.close() c.mutex.Lock() @@ -680,6 +702,58 @@ func (c *Conn) MagicsockServeHTTPDebug(w http.ResponseWriter, r *http.Request) { c.magicConn.ServeHTTPDebug(w, r) } +func (c *Conn) connectedTelemetryEvent() error { + if c.telemetrySink == nil { + return nil + } + e, err := c.newTelemetryEvent() + if err != nil { + return xerrors.Errorf("create telemetry event: %w", err) + } + e.Status = proto.TelemetryEvent_CONNECTED + c.telemetryWg.Add(1) + go func() { + defer c.telemetryWg.Done() + c.telemetrySink.SendTelemetryEvent(e) + }() + return nil +} + +func (c *Conn) newTelemetryEvent() (*proto.TelemetryEvent, error) { + id, err := c.id.MarshalBinary() + if err != nil { + return nil, xerrors.Errorf("marshal uuid to bytes: %w", err) + } + c.nodeUpdater.L.Lock() + node := c.nodeUpdater.nodeLocked() + c.nodeUpdater.L.Unlock() + + logs, ips := c.telemetryLogs.getLogs() + return &proto.TelemetryEvent{ + Id: id, + Time: timestamppb.Now(), + ClientType: c.clientType, + NodeIdSelf: uint64(node.ID), + Logs: logs, + LogIpHashes: ips, + + // TODO: + Application: "", + NodeIdRemote: 0, + P2PEndpoint: &proto.TelemetryEvent_P2PEndpoint{}, + ThroughputMbits: &wrapperspb.FloatValue{}, + HomeDerp: "", + DerpMap: &proto.DERPMap{}, + LatestNetcheck: &proto.Netcheck{}, + ConnectionAge: &durationpb.Duration{}, + ConnectionSetup: &durationpb.Duration{}, + P2PSetup: &durationpb.Duration{}, + // TODO: One of these two + DerpLatency: &durationpb.Duration{}, + P2PLatency: &durationpb.Duration{}, + }, nil +} + // PeerDiagnostics is a checklist of human-readable conditions necessary to establish an encrypted // tunnel to a peer via a Conn type PeerDiagnostics struct { @@ -748,8 +822,12 @@ type addr struct{ ln *listener } func (a addr) Network() string { return a.ln.key.network } func (a addr) String() string { return a.ln.addr } -// Logger converts the Tailscale logging function to use slog. -func Logger(logger slog.Logger) tslogger.Logf { +// Logger converts the Tailscale logging function to use a slog-compatible +// logger. +func Logger(logger interface { + Debug(ctx context.Context, str string, args ...any) +}, +) tslogger.Logf { return tslogger.Logf(func(format string, args ...any) { slog.Helper() logger.Debug(context.Background(), fmt.Sprintf(format, args...)) diff --git a/tailnet/logger.go b/tailnet/logger.go index 044fcde0a9ca3..8ff2ddba675a5 100644 --- a/tailnet/logger.go +++ b/tailnet/logger.go @@ -90,6 +90,7 @@ func (m multiLogger) With(fields ...slog.Field) multiLogger { type bufferLogSink struct { sink slog.Sink mu sync.Mutex + // TODO: Store only useful logs logs []string // We use the same salt so the same IP hashes to the same value. hashSalt string diff --git a/tailnet/logger_internal_test.go b/tailnet/logger_internal_test.go index 7b0b253c39672..a0b5d725a8bc0 100644 --- a/tailnet/logger_internal_test.go +++ b/tailnet/logger_internal_test.go @@ -7,9 +7,8 @@ import ( "github.com/stretchr/testify/require" - "github.com/coder/coder/v2/tailnet/proto" - "cdr.dev/slog" + "github.com/coder/coder/v2/tailnet/proto" ) func TestBufferLogSink(t *testing.T) { diff --git a/tailnet/node.go b/tailnet/node.go index 858af3ad71e24..165bd85b0314c 100644 --- a/tailnet/node.go +++ b/tailnet/node.go @@ -22,7 +22,7 @@ type nodeUpdater struct { closing bool // static - logger slog.Logger + logger multiLogger id tailcfg.NodeID key key.NodePublic discoKey key.DiscoPublic @@ -96,7 +96,7 @@ func (u *nodeUpdater) close() { } func newNodeUpdater( - logger slog.Logger, callback func(n *Node), + logger multiLogger, callback func(n *Node), id tailcfg.NodeID, np key.NodePublic, dp key.DiscoPublic, ) *nodeUpdater { u := &nodeUpdater{ diff --git a/tailnet/node_internal_test.go b/tailnet/node_internal_test.go index 577ce55a832cc..6c471f9b67e28 100644 --- a/tailnet/node_internal_test.go +++ b/tailnet/node_internal_test.go @@ -22,7 +22,7 @@ import ( func TestNodeUpdater_setNetInfo_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -75,7 +75,7 @@ func TestNodeUpdater_setNetInfo_different(t *testing.T) { func TestNodeUpdater_setNetInfo_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -116,7 +116,7 @@ func TestNodeUpdater_setNetInfo_same(t *testing.T) { func TestNodeUpdater_setDERPForcedWebsocket_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -155,7 +155,7 @@ func TestNodeUpdater_setDERPForcedWebsocket_different(t *testing.T) { func TestNodeUpdater_setDERPForcedWebsocket_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -193,7 +193,7 @@ func TestNodeUpdater_setDERPForcedWebsocket_same(t *testing.T) { func TestNodeUpdater_setStatus_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -243,7 +243,7 @@ func TestNodeUpdater_setStatus_different(t *testing.T) { func TestNodeUpdater_setStatus_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -283,7 +283,7 @@ func TestNodeUpdater_setStatus_same(t *testing.T) { func TestNodeUpdater_setStatus_error(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -321,7 +321,7 @@ func TestNodeUpdater_setStatus_error(t *testing.T) { func TestNodeUpdater_setStatus_outdated(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -363,7 +363,7 @@ func TestNodeUpdater_setStatus_outdated(t *testing.T) { func TestNodeUpdater_setAddresses_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -404,7 +404,7 @@ func TestNodeUpdater_setAddresses_different(t *testing.T) { func TestNodeUpdater_setAddresses_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -443,7 +443,7 @@ func TestNodeUpdater_setAddresses_same(t *testing.T) { func TestNodeUpdater_setCallback(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -484,7 +484,7 @@ func TestNodeUpdater_setCallback(t *testing.T) { func TestNodeUpdater_setBlockEndpoints_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -533,7 +533,7 @@ func TestNodeUpdater_setBlockEndpoints_different(t *testing.T) { func TestNodeUpdater_setBlockEndpoints_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -571,7 +571,7 @@ func TestNodeUpdater_setBlockEndpoints_same(t *testing.T) { func TestNodeUpdater_fillPeerDiagnostics(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -619,7 +619,7 @@ func TestNodeUpdater_fillPeerDiagnostics(t *testing.T) { func TestNodeUpdater_fillPeerDiagnostics_noCallback(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() diff --git a/tailnet/proto/tailnet.pb.go b/tailnet/proto/tailnet.pb.go index a9268f04d992c..1990c40938499 100644 --- a/tailnet/proto/tailnet.pb.go +++ b/tailnet/proto/tailnet.pb.go @@ -81,11 +81,10 @@ func (CoordinateResponse_PeerUpdate_Kind) EnumDescriptor() ([]byte, []int) { type IPFields_IPClass int32 const ( - IPFields_PUBLIC IPFields_IPClass = 0 - IPFields_PRIVATE IPFields_IPClass = 1 - IPFields_LINK_LOCAL IPFields_IPClass = 2 - IPFields_UNIQUE_LOCAL IPFields_IPClass = 3 - IPFields_LOOPBACK IPFields_IPClass = 4 + IPFields_PUBLIC IPFields_IPClass = 0 + IPFields_PRIVATE IPFields_IPClass = 1 + IPFields_LINK_LOCAL IPFields_IPClass = 2 + IPFields_LOOPBACK IPFields_IPClass = 3 ) // Enum value maps for IPFields_IPClass. @@ -94,15 +93,13 @@ var ( 0: "PUBLIC", 1: "PRIVATE", 2: "LINK_LOCAL", - 3: "UNIQUE_LOCAL", - 4: "LOOPBACK", + 3: "LOOPBACK", } IPFields_IPClass_value = map[string]int32{ - "PUBLIC": 0, - "PRIVATE": 1, - "LINK_LOCAL": 2, - "UNIQUE_LOCAL": 3, - "LOOPBACK": 4, + "PUBLIC": 0, + "PRIVATE": 1, + "LINK_LOCAL": 2, + "LOOPBACK": 3, } ) @@ -1930,218 +1927,217 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4c, 0x4f, 0x53, 0x54, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x41, 0x44, 0x59, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x48, 0x41, 0x4e, 0x44, 0x53, - 0x48, 0x41, 0x4b, 0x45, 0x10, 0x04, 0x22, 0xb2, 0x01, 0x0a, 0x08, 0x49, 0x50, 0x46, 0x69, 0x65, + 0x48, 0x41, 0x4b, 0x45, 0x10, 0x04, 0x22, 0xa0, 0x01, 0x0a, 0x08, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x2e, 0x49, 0x50, 0x43, 0x6c, 0x61, 0x73, 0x73, - 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x22, 0x52, 0x0a, 0x07, 0x49, 0x50, 0x43, 0x6c, 0x61, + 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x22, 0x40, 0x0a, 0x07, 0x49, 0x50, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x4c, - 0x49, 0x4e, 0x4b, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x55, - 0x4e, 0x49, 0x51, 0x55, 0x45, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x03, 0x12, 0x0c, 0x0a, - 0x08, 0x4c, 0x4f, 0x4f, 0x50, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x04, 0x22, 0xc4, 0x0a, 0x0a, 0x08, - 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x55, 0x44, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x49, 0x50, - 0x76, 0x36, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x49, 0x50, 0x76, 0x36, 0x12, 0x12, - 0x0a, 0x04, 0x49, 0x50, 0x76, 0x34, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x49, 0x50, - 0x76, 0x34, 0x12, 0x20, 0x0a, 0x0b, 0x49, 0x50, 0x76, 0x36, 0x43, 0x61, 0x6e, 0x53, 0x65, 0x6e, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x49, 0x50, 0x76, 0x36, 0x43, 0x61, 0x6e, - 0x53, 0x65, 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x49, 0x50, 0x76, 0x34, 0x43, 0x61, 0x6e, 0x53, - 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x49, 0x50, 0x76, 0x34, 0x43, - 0x61, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x4f, 0x53, 0x48, 0x61, 0x73, 0x49, - 0x50, 0x76, 0x36, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x4f, 0x53, 0x48, 0x61, 0x73, - 0x49, 0x50, 0x76, 0x36, 0x12, 0x16, 0x0a, 0x06, 0x49, 0x43, 0x4d, 0x50, 0x76, 0x34, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x49, 0x43, 0x4d, 0x50, 0x76, 0x34, 0x12, 0x50, 0x0a, 0x15, - 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x72, 0x69, 0x65, 0x73, 0x42, 0x79, 0x44, - 0x65, 0x73, 0x74, 0x49, 0x50, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, - 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x15, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, - 0x56, 0x61, 0x72, 0x69, 0x65, 0x73, 0x42, 0x79, 0x44, 0x65, 0x73, 0x74, 0x49, 0x50, 0x12, 0x3c, - 0x0a, 0x0b, 0x48, 0x61, 0x69, 0x72, 0x50, 0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, - 0x0b, 0x48, 0x61, 0x69, 0x72, 0x50, 0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x2e, 0x0a, 0x04, - 0x55, 0x50, 0x6e, 0x50, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, - 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x55, 0x50, 0x6e, 0x50, 0x12, 0x2c, 0x0a, 0x03, - 0x50, 0x4d, 0x50, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x49, 0x4e, 0x4b, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x4c, + 0x4f, 0x4f, 0x50, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x03, 0x22, 0xc4, 0x0a, 0x0a, 0x08, 0x4e, 0x65, + 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x03, 0x55, 0x44, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x49, 0x50, 0x76, 0x36, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x49, 0x50, 0x76, 0x36, 0x12, 0x12, 0x0a, 0x04, + 0x49, 0x50, 0x76, 0x34, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x49, 0x50, 0x76, 0x34, + 0x12, 0x20, 0x0a, 0x0b, 0x49, 0x50, 0x76, 0x36, 0x43, 0x61, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x49, 0x50, 0x76, 0x36, 0x43, 0x61, 0x6e, 0x53, 0x65, + 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x49, 0x50, 0x76, 0x34, 0x43, 0x61, 0x6e, 0x53, 0x65, 0x6e, + 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x49, 0x50, 0x76, 0x34, 0x43, 0x61, 0x6e, + 0x53, 0x65, 0x6e, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x4f, 0x53, 0x48, 0x61, 0x73, 0x49, 0x50, 0x76, + 0x36, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x4f, 0x53, 0x48, 0x61, 0x73, 0x49, 0x50, + 0x76, 0x36, 0x12, 0x16, 0x0a, 0x06, 0x49, 0x43, 0x4d, 0x50, 0x76, 0x34, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x06, 0x49, 0x43, 0x4d, 0x50, 0x76, 0x34, 0x12, 0x50, 0x0a, 0x15, 0x4d, 0x61, + 0x70, 0x70, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x72, 0x69, 0x65, 0x73, 0x42, 0x79, 0x44, 0x65, 0x73, + 0x74, 0x49, 0x50, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x50, 0x4d, 0x50, 0x12, 0x2c, 0x0a, 0x03, 0x50, 0x43, - 0x50, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x15, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x56, 0x61, + 0x72, 0x69, 0x65, 0x73, 0x42, 0x79, 0x44, 0x65, 0x73, 0x74, 0x49, 0x50, 0x12, 0x3c, 0x0a, 0x0b, + 0x48, 0x61, 0x69, 0x72, 0x50, 0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0b, 0x48, + 0x61, 0x69, 0x72, 0x50, 0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x2e, 0x0a, 0x04, 0x55, 0x50, + 0x6e, 0x50, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x55, 0x50, 0x6e, 0x50, 0x12, 0x2c, 0x0a, 0x03, 0x50, 0x4d, + 0x50, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x52, 0x03, 0x50, 0x43, 0x50, 0x12, 0x24, 0x0a, 0x0d, 0x50, 0x72, 0x65, 0x66, - 0x65, 0x72, 0x72, 0x65, 0x64, 0x44, 0x45, 0x52, 0x50, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0d, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x44, 0x45, 0x52, 0x50, 0x12, 0x53, - 0x0a, 0x0d, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, - 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, + 0x6c, 0x75, 0x65, 0x52, 0x03, 0x50, 0x4d, 0x50, 0x12, 0x2c, 0x0a, 0x03, 0x50, 0x43, 0x50, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x03, 0x50, 0x43, 0x50, 0x12, 0x24, 0x0a, 0x0d, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, + 0x72, 0x65, 0x64, 0x44, 0x45, 0x52, 0x50, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x50, + 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x44, 0x45, 0x52, 0x50, 0x12, 0x53, 0x0a, 0x0d, + 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0e, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, + 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, + 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x0d, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, + 0x79, 0x12, 0x59, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, + 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, + 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, + 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x52, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x59, 0x0a, 0x0f, + 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, + 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4c, 0x61, 0x74, 0x65, - 0x6e, 0x63, 0x79, 0x12, 0x59, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, - 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, + 0x6b, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, + 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, + 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, + 0x6c, 0x56, 0x34, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x50, + 0x52, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x34, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x6c, + 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x36, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, - 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x52, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x59, - 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, - 0x79, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, - 0x65, 0x63, 0x6b, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, - 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x6c, 0x6f, - 0x62, 0x61, 0x6c, 0x56, 0x34, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, - 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, - 0x49, 0x50, 0x52, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x34, 0x12, 0x41, 0x0a, 0x08, - 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x36, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, - 0x65, 0x63, 0x6b, 0x49, 0x50, 0x52, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x36, 0x12, - 0x40, 0x0a, 0x0d, 0x43, 0x61, 0x70, 0x74, 0x69, 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, - 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x52, 0x0d, 0x43, 0x61, 0x70, 0x74, 0x69, 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x61, - 0x6c, 0x1a, 0x5b, 0x0a, 0x12, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, - 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5d, - 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, - 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5d, 0x0a, - 0x14, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, + 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x49, 0x50, 0x52, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x36, 0x12, 0x40, 0x0a, + 0x0d, 0x43, 0x61, 0x70, 0x74, 0x69, 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x18, 0x13, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x52, 0x0d, 0x43, 0x61, 0x70, 0x74, 0x69, 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x1a, + 0x5b, 0x0a, 0x12, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x54, 0x0a, 0x0a, - 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, - 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x32, - 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5d, 0x0a, 0x14, + 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5d, 0x0a, 0x14, 0x52, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x54, 0x0a, 0x0a, 0x4e, 0x65, + 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x06, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, + 0x22, 0xff, 0x0a, 0x0a, 0x0e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, + 0x69, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, + 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, + 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x4c, 0x0a, 0x0b, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x73, 0x22, 0xff, 0x0a, 0x0a, 0x0e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, - 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x69, 0x73, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x4c, 0x0a, 0x0b, 0x63, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x63, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, - 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x53, 0x65, 0x6c, 0x66, 0x12, 0x24, 0x0a, 0x0e, 0x6e, - 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x52, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x70, 0x32, 0x70, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x5f, + 0x69, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x6e, + 0x6f, 0x64, 0x65, 0x49, 0x64, 0x53, 0x65, 0x6c, 0x66, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x6f, 0x64, + 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x12, + 0x4f, 0x0a, 0x0c, 0x70, 0x32, 0x70, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, + 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, + 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x32, 0x70, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x12, 0x55, 0x0a, 0x0d, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, + 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, - 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x32, 0x70, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x12, 0x55, 0x0a, 0x0d, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x70, 0x5f, 0x68, 0x61, 0x73, - 0x68, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, - 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x6f, 0x67, 0x49, - 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x6c, 0x6f, - 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x6d, - 0x65, 0x5f, 0x64, 0x65, 0x72, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, - 0x6d, 0x65, 0x44, 0x65, 0x72, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0c, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x34, 0x0a, 0x08, 0x64, 0x65, - 0x72, 0x70, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x52, 0x07, 0x64, 0x65, 0x72, 0x70, 0x4d, 0x61, 0x70, - 0x12, 0x43, 0x0a, 0x0f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6e, 0x65, 0x74, 0x63, 0x68, - 0x65, 0x63, 0x6b, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, - 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0e, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x4e, 0x65, 0x74, - 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x40, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x67, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x74, 0x75, 0x70, 0x18, 0x10, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, 0x36, 0x0a, - 0x09, 0x70, 0x32, 0x70, 0x5f, 0x73, 0x65, 0x74, 0x75, 0x70, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x70, 0x32, 0x70, - 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, 0x3c, 0x0a, 0x0c, 0x64, 0x65, 0x72, 0x70, 0x5f, 0x6c, 0x61, - 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, + 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x6f, 0x67, 0x49, 0x70, 0x48, + 0x61, 0x73, 0x68, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x6c, 0x6f, 0x67, 0x49, + 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x6d, 0x65, 0x5f, + 0x64, 0x65, 0x72, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x6d, 0x65, + 0x44, 0x65, 0x72, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x34, 0x0a, 0x08, 0x64, 0x65, 0x72, 0x70, + 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, + 0x52, 0x50, 0x4d, 0x61, 0x70, 0x52, 0x07, 0x64, 0x65, 0x72, 0x70, 0x4d, 0x61, 0x70, 0x12, 0x43, + 0x0a, 0x0f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, + 0x65, 0x63, 0x6b, 0x52, 0x0e, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x4e, 0x65, 0x74, 0x63, 0x68, + 0x65, 0x63, 0x6b, 0x12, 0x40, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x72, 0x70, 0x4c, 0x61, 0x74, 0x65, - 0x6e, 0x63, 0x79, 0x12, 0x3a, 0x0a, 0x0b, 0x70, 0x32, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, - 0x63, 0x79, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x70, 0x32, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, - 0x46, 0x0a, 0x10, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x62, - 0x69, 0x74, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x6c, 0x6f, 0x61, - 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x70, - 0x75, 0x74, 0x4d, 0x62, 0x69, 0x74, 0x73, 0x1a, 0x69, 0x0a, 0x0b, 0x50, 0x32, 0x50, 0x45, 0x6e, - 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, - 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x32, - 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x73, 0x1a, 0x5a, 0x0a, 0x10, 0x4c, 0x6f, 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, - 0x6c, 0x64, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x29, - 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x4e, - 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, - 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x01, 0x22, 0x39, 0x0a, 0x0a, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x4c, 0x49, 0x10, 0x00, - 0x12, 0x09, 0x0a, 0x05, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x43, - 0x4f, 0x44, 0x45, 0x52, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x53, 0x50, 0x52, 0x4f, - 0x58, 0x59, 0x10, 0x03, 0x22, 0x4c, 0x0a, 0x10, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, - 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x98, 0x02, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6c, - 0x6e, 0x65, 0x74, 0x12, 0x58, 0x0a, 0x0d, 0x50, 0x6f, 0x73, 0x74, 0x54, 0x65, 0x6c, 0x65, 0x6d, - 0x65, 0x74, 0x72, 0x79, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, - 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, - 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, - 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x12, - 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, - 0x4d, 0x61, 0x70, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, - 0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, - 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, - 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, - 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, - 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x41, 0x67, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x74, 0x75, 0x70, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, 0x36, 0x0a, 0x09, 0x70, + 0x32, 0x70, 0x5f, 0x73, 0x65, 0x74, 0x75, 0x70, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x70, 0x32, 0x70, 0x53, 0x65, + 0x74, 0x75, 0x70, 0x12, 0x3c, 0x0a, 0x0c, 0x64, 0x65, 0x72, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, + 0x6e, 0x63, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x72, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, + 0x79, 0x12, 0x3a, 0x0a, 0x0b, 0x70, 0x32, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, + 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x0a, 0x70, 0x32, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x46, 0x0a, + 0x10, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x62, 0x69, 0x74, + 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, + 0x4d, 0x62, 0x69, 0x74, 0x73, 0x1a, 0x69, 0x0a, 0x0b, 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x06, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, + 0x1a, 0x5a, 0x0a, 0x10, 0x4c, 0x6f, 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, + 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x29, 0x0a, 0x06, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, + 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, + 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x01, 0x22, 0x39, 0x0a, 0x0a, 0x43, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x4c, 0x49, 0x10, 0x00, 0x12, 0x09, + 0x0a, 0x05, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x44, + 0x45, 0x52, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x53, 0x50, 0x52, 0x4f, 0x58, 0x59, + 0x10, 0x03, 0x22, 0x4c, 0x0a, 0x10, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, + 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, + 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, + 0x22, 0x13, 0x0a, 0x11, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x98, 0x02, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6c, 0x6e, 0x65, + 0x74, 0x12, 0x58, 0x0a, 0x0d, 0x50, 0x6f, 0x73, 0x74, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, + 0x72, 0x79, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, + 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, + 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, + 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0e, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x27, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, + 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, + 0x70, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, + 0x65, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, + 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, + 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, + 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x61, + 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( diff --git a/tailnet/proto/tailnet.proto b/tailnet/proto/tailnet.proto index b8e97d8a7a493..b96c1e7b3c997 100644 --- a/tailnet/proto/tailnet.proto +++ b/tailnet/proto/tailnet.proto @@ -108,8 +108,7 @@ message IPFields { PUBLIC = 0; PRIVATE = 1; LINK_LOCAL = 2; - UNIQUE_LOCAL = 3; - LOOPBACK = 4; + LOOPBACK = 3; } IPClass class = 2; } From 66313d1881785cfeddb7d3e5afa56356050b32c7 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Fri, 28 Jun 2024 07:14:02 +0000 Subject: [PATCH 05/19] derpmap telemetry clean --- tailnet/configmaps.go | 6 +- tailnet/conn.go | 17 +++-- tailnet/logger_internal_test.go | 66 ++++++++++++++++-- tailnet/{logger.go => telemetry.go} | 100 ++++++++++++++++++++-------- 4 files changed, 148 insertions(+), 41 deletions(-) rename tailnet/{logger.go => telemetry.go} (62%) diff --git a/tailnet/configmaps.go b/tailnet/configmaps.go index 2f835a2875853..1df3a13264d8c 100644 --- a/tailnet/configmaps.go +++ b/tailnet/configmaps.go @@ -283,15 +283,17 @@ func (c *configMaps) getBlockEndpoints() bool { // setDERPMap sets the DERP map, triggering a configuration of the engine if it has changed. // c.L MUST NOT be held. -func (c *configMaps) setDERPMap(derpMap *tailcfg.DERPMap) { +// Returns if the derpMap is dirty. +func (c *configMaps) setDERPMap(derpMap *tailcfg.DERPMap) bool { c.L.Lock() defer c.L.Unlock() if CompareDERPMaps(c.derpMap, derpMap) { - return + return false } c.derpMap = derpMap c.derpMapDirty = true c.Broadcast() + return true } // derMapLocked returns the current DERPMap. c.L must be held diff --git a/tailnet/conn.go b/tailnet/conn.go index fdbbbc4ffc11f..a582adaf674d3 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -138,11 +138,11 @@ func NewConn(options *Options) (conn *Conn, err error) { var ( logger = newMultiLogger(options.Logger) - telemetryLogSink *bufferLogSink + telemetryLogSink *TelemetryStore ) if options.TelemetrySink != nil { var err error - telemetryLogSink, err = newBufferLogSink() + telemetryLogSink, err = newTelemetryStore() if err != nil { return nil, xerrors.Errorf("create telemetry log sink: %w", err) } @@ -352,7 +352,7 @@ type Conn struct { telemetrySink TelemetrySink // telemetryLogs will be nil if telemetrySink is nil. - telemetryLogs *bufferLogSink + telemetryLogs *TelemetryStore telemetryWg sync.WaitGroup trafficStats *connstats.Statistics @@ -388,7 +388,9 @@ func (c *Conn) SetNodeCallback(callback func(node *Node)) { // SetDERPMap updates the DERPMap of a connection. func (c *Conn) SetDERPMap(derpMap *tailcfg.DERPMap) { - c.configMaps.setDERPMap(derpMap) + if c.configMaps.setDERPMap(derpMap) && c.telemetryLogs != nil { + c.telemetryLogs.updateDerpMap(derpMap) + } } func (c *Conn) SetDERPForceWebSockets(v bool) { @@ -512,6 +514,8 @@ func (c *Conn) AwaitReachable(ctx context.Context, ip netip.Addr) bool { for { select { case <-completedCtx.Done(): + // TODO(ethanndickson): For now, I'm interpreting 'connected' as when the + // agent is reachable. _ = c.connectedTelemetryEvent() return true case <-t.C: @@ -719,6 +723,7 @@ func (c *Conn) connectedTelemetryEvent() error { return nil } +// The returned telemetry event will not have it's status set. func (c *Conn) newTelemetryEvent() (*proto.TelemetryEvent, error) { id, err := c.id.MarshalBinary() if err != nil { @@ -728,7 +733,7 @@ func (c *Conn) newTelemetryEvent() (*proto.TelemetryEvent, error) { node := c.nodeUpdater.nodeLocked() c.nodeUpdater.L.Unlock() - logs, ips := c.telemetryLogs.getLogs() + logs, ips, dm := c.telemetryLogs.getStore() return &proto.TelemetryEvent{ Id: id, Time: timestamppb.Now(), @@ -736,6 +741,7 @@ func (c *Conn) newTelemetryEvent() (*proto.TelemetryEvent, error) { NodeIdSelf: uint64(node.ID), Logs: logs, LogIpHashes: ips, + DerpMap: DERPMapToProto(dm), // TODO: Application: "", @@ -743,7 +749,6 @@ func (c *Conn) newTelemetryEvent() (*proto.TelemetryEvent, error) { P2PEndpoint: &proto.TelemetryEvent_P2PEndpoint{}, ThroughputMbits: &wrapperspb.FloatValue{}, HomeDerp: "", - DerpMap: &proto.DERPMap{}, LatestNetcheck: &proto.Netcheck{}, ConnectionAge: &durationpb.Duration{}, ConnectionSetup: &durationpb.Duration{}, diff --git a/tailnet/logger_internal_test.go b/tailnet/logger_internal_test.go index a0b5d725a8bc0..81485fce26bd3 100644 --- a/tailnet/logger_internal_test.go +++ b/tailnet/logger_internal_test.go @@ -6,18 +6,19 @@ import ( "testing" "github.com/stretchr/testify/require" + "tailscale.com/tailcfg" "cdr.dev/slog" "github.com/coder/coder/v2/tailnet/proto" ) -func TestBufferLogSink(t *testing.T) { +func TestTelemetryStore(t *testing.T) { t.Parallel() t.Run("NoIP", func(t *testing.T) { t.Parallel() ctx := context.Background() - sink, err := newBufferLogSink() + sink, err := newTelemetryStore() require.NoError(t, err) logger := slog.Make(sink).Leveled(slog.LevelDebug) @@ -25,7 +26,7 @@ func TestBufferLogSink(t *testing.T) { logger.Debug(ctx, "line2 fe80") logger.Debug(ctx, "line3 xxxx::x") - logs, hashes := sink.getLogs() + logs, hashes, _ := sink.getStore() require.Len(t, logs, 3) require.Len(t, hashes, 0) require.Contains(t, logs[0], "line1") @@ -103,7 +104,7 @@ func TestBufferLogSink(t *testing.T) { t.Run(c.name, func(t *testing.T) { t.Parallel() ctx := context.Background() - sink, err := newBufferLogSink() + sink, err := newTelemetryStore() require.NoError(t, err) logger := slog.Make(sink).Leveled(slog.LevelDebug) @@ -116,15 +117,15 @@ func TestBufferLogSink(t *testing.T) { logger.Debug(ctx, fmt.Sprintf("line2: %s/24", c.ip)) logger.Debug(ctx, fmt.Sprintf("line3: %s foo (%s)", ipWithPort, c.ip)) - logs, hashes := sink.getLogs() + logs, ips, _ := sink.getStore() require.Len(t, logs, 3) - require.Len(t, hashes, 1) + require.Len(t, ips, 1) for _, log := range logs { t.Log(log) } // This only runs once since we only processed a single IP. - for expectedHash, ipFields := range hashes { + for expectedHash, ipFields := range ips { hashedIPWithPort := expectedHash + ":8080" if c.expectedVersion == 6 { hashedIPWithPort = fmt.Sprintf("[%s]:8080", expectedHash) @@ -141,4 +142,55 @@ func TestBufferLogSink(t *testing.T) { }) } }) + + t.Run("DerpMapClean", func(t *testing.T) { + t.Parallel() + ctx := context.Background() + telemetry, err := newTelemetryStore() + require.NoError(t, err) + logger := slog.Make(telemetry).Leveled(slog.LevelDebug) + + derpMap := &tailcfg.DERPMap{ + Regions: make(map[int]*tailcfg.DERPRegion), + } + // Add a region and node that uses every single field. + derpMap.Regions[999] = &tailcfg.DERPRegion{ + RegionID: 999, + EmbeddedRelay: true, + RegionCode: "zzz", + RegionName: "Cool Region", + Avoid: true, + + Nodes: []*tailcfg.DERPNode{ + { + Name: "zzz1", + RegionID: 999, + HostName: "coolderp.com", + CertName: "coolderpcert", + IPv4: "1.2.3.4", + IPv6: "2001:db8::1", + STUNTestIP: "5.6.7.8", + }, + }, + } + telemetry.updateDerpMap(derpMap) + + logger.Debug(ctx, "line1 coolderp.com qwerty") + logger.Debug(ctx, "line2 1.2.3.4 asdf") + logger.Debug(ctx, "line3 2001:db8::1 foo") + + logs, ips, dm := telemetry.getStore() + require.Len(t, logs, 3) + require.Len(t, ips, 3) + require.Len(t, dm.Regions[999].Nodes, 1) + node := dm.Regions[999].Nodes[0] + require.NotContains(t, node.HostName, "coolderp.com") + require.NotContains(t, node.IPv4, "1.2.3.4") + require.NotContains(t, node.IPv6, "2001:db8::1") + require.NotContains(t, node.STUNTestIP, "5.6.7.8") + require.Contains(t, logs[0], node.HostName) + require.Contains(t, ips, node.STUNTestIP) + require.Contains(t, ips, node.IPv6) + require.Contains(t, ips, node.IPv4) + }) } diff --git a/tailnet/logger.go b/tailnet/telemetry.go similarity index 62% rename from tailnet/logger.go rename to tailnet/telemetry.go index 8ff2ddba675a5..534bff627d80d 100644 --- a/tailnet/logger.go +++ b/tailnet/telemetry.go @@ -11,6 +11,7 @@ import ( "sync" "golang.org/x/xerrors" + "tailscale.com/tailcfg" "cdr.dev/slog" "cdr.dev/slog/sloggers/sloghuman" @@ -87,45 +88,81 @@ func (m multiLogger) With(fields ...slog.Field) multiLogger { // A logger sink that extracts (anonymized) IP addresses from logs for building // network telemetry events -type bufferLogSink struct { +type TelemetryStore struct { + // Always self-referential sink slog.Sink mu sync.Mutex // TODO: Store only useful logs - logs []string - // We use the same salt so the same IP hashes to the same value. + logs []string hashSalt string - // A cache to avoid hashing the same IP multiple times. - ipToHash map[string]string + // A cache to avoid hashing the same IP or hostname multiple times. + hashCache map[string]string hashedIPs map[string]*proto.IPFields + + cleanDerpMap *tailcfg.DERPMap + derpMapFilter *regexp.Regexp } -var _ slog.Sink = &bufferLogSink{} +var _ slog.Sink = &TelemetryStore{} -var _ io.Writer = &bufferLogSink{} +var _ io.Writer = &TelemetryStore{} -func newBufferLogSink() (*bufferLogSink, error) { +func newTelemetryStore() (*TelemetryStore, error) { hashSalt, err := cryptorand.String(16) if err != nil { return nil, err } - out := &bufferLogSink{ - logs: []string{}, - hashSalt: hashSalt, - ipToHash: make(map[string]string), - hashedIPs: make(map[string]*proto.IPFields), + out := &TelemetryStore{ + logs: []string{}, + hashSalt: hashSalt, + hashCache: make(map[string]string), + hashedIPs: make(map[string]*proto.IPFields), + derpMapFilter: regexp.MustCompile(`^$`), } out.sink = sloghuman.Sink(out) return out, nil } -func (b *bufferLogSink) getLogs() ([]string, map[string]*proto.IPFields) { +func (b *TelemetryStore) getStore() ([]string, map[string]*proto.IPFields, *tailcfg.DERPMap) { b.mu.Lock() defer b.mu.Unlock() - return append([]string{}, b.logs...), b.hashedIPs + return append([]string{}, b.logs...), b.hashedIPs, b.cleanDerpMap.Clone() +} + +// Given a DERPMap, anonymise all IPs and hostnames. +// Keep track of seen hostnames/cert names to anonymize them from future logs. +// b.mu must NOT be held. +func (b *TelemetryStore) updateDerpMap(cur *tailcfg.DERPMap) { + b.mu.Lock() + defer b.mu.Unlock() + var names []string + cleanMap := cur.Clone() + for _, r := range cleanMap.Regions { + for _, n := range r.Nodes { + escapedName := regexp.QuoteMeta(n.HostName) + escapedCertName := regexp.QuoteMeta(n.CertName) + names = append(names, escapedName, escapedCertName) + + ipv4, _ := b.processIPLocked(n.IPv4) + n.IPv4 = ipv4 + ipv6, _ := b.processIPLocked(n.IPv6) + n.IPv6 = ipv6 + stunIP, _ := b.processIPLocked(n.STUNTestIP) + n.STUNTestIP = stunIP + hn := b.hashAddr(n.HostName) + n.HostName = hn + cn := b.hashAddr(n.CertName) + n.CertName = cn + } + } + if len(names) != 0 { + b.derpMapFilter = regexp.MustCompile((strings.Join(names, "|"))) + } + b.cleanDerpMap = cleanMap } // Write implements io.Writer. -func (b *bufferLogSink) Write(p []byte) (n int, err error) { +func (b *TelemetryStore) Write(p []byte) (n int, err error) { b.mu.Lock() defer b.mu.Unlock() @@ -138,25 +175,31 @@ func (b *bufferLogSink) Write(p []byte) (n int, err error) { if len(logLineAfterLevel) == 2 { logLineAfterLevel = logLineSplit[1] } + // Anonymize IP addresses for _, match := range ipv4And6Regex.FindAllString(logLineAfterLevel, -1) { hash, err := b.processIPLocked(match) if err == nil { logLine = strings.ReplaceAll(logLine, match, hash) } } + // Anonymize derp map host names + for _, match := range b.derpMapFilter.FindAllString(logLineAfterLevel, -1) { + hash := b.hashAddr(match) + logLine = strings.ReplaceAll(logLine, match, hash) + } b.logs = append(b.logs, logLine) return len(p), nil } // LogEntry implements slog.Sink. -func (b *bufferLogSink) LogEntry(ctx context.Context, e slog.SinkEntry) { +func (b *TelemetryStore) LogEntry(ctx context.Context, e slog.SinkEntry) { // This will call (*bufferLogSink).Write b.sink.LogEntry(ctx, e) } // Sync implements slog.Sink. -func (b *bufferLogSink) Sync() { +func (b *TelemetryStore) Sync() { b.sink.Sync() } @@ -164,11 +207,7 @@ func (b *bufferLogSink) Sync() { // to the cache. It will also add it to hashedIPs. // // b.mu must be held. -func (b *bufferLogSink) processIPLocked(ip string) (string, error) { - if hashStr, ok := b.ipToHash[ip]; ok { - return hashStr, nil - } - +func (b *TelemetryStore) processIPLocked(ip string) (string, error) { addr, err := netip.ParseAddr(ip) if err != nil { return "", xerrors.Errorf("failed to parse IP %q: %w", ip, err) @@ -190,12 +229,21 @@ func (b *bufferLogSink) processIPLocked(ip string) (string, error) { class = proto.IPFields_PRIVATE } - hash := sha256.Sum256([]byte(b.hashSalt + ip)) - hashStr := hex.EncodeToString(hash[:]) - b.ipToHash[ip] = hashStr + hashStr := b.hashAddr(ip) b.hashedIPs[hashStr] = &proto.IPFields{ Version: version, Class: class, } return hashStr, nil } + +func (b *TelemetryStore) hashAddr(addr string) string { + if hashStr, ok := b.hashCache[addr]; ok { + return hashStr + } + + hash := sha256.Sum256([]byte(b.hashSalt + addr)) + hashStr := hex.EncodeToString(hash[:]) + b.hashCache[addr] = hashStr + return hashStr +} From 09d92490905c5790d6104e46f0852885f977de6a Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Fri, 28 Jun 2024 08:52:38 +0000 Subject: [PATCH 06/19] netcheck stub --- tailnet/conn.go | 46 +++++++++++++++++---------------- tailnet/convert.go | 10 +++++++ tailnet/logger_internal_test.go | 6 ++--- tailnet/telemetry.go | 14 ++++++++-- 4 files changed, 49 insertions(+), 27 deletions(-) diff --git a/tailnet/conn.go b/tailnet/conn.go index a582adaf674d3..773bc656334f0 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -137,16 +137,16 @@ func NewConn(options *Options) (conn *Conn, err error) { } var ( - logger = newMultiLogger(options.Logger) - telemetryLogSink *TelemetryStore + logger = newMultiLogger(options.Logger) + telemetryStore *TelemetryStore ) if options.TelemetrySink != nil { var err error - telemetryLogSink, err = newTelemetryStore() + telemetryStore, err = newTelemetryStore() if err != nil { return nil, xerrors.Errorf("create telemetry log sink: %w", err) } - logger = logger.appendLogger(slog.Make(telemetryLogSink).Leveled(slog.LevelDebug)) + logger = logger.appendLogger(slog.Make(telemetryStore).Leveled(slog.LevelDebug)) } nodePrivateKey := key.NewNode() @@ -270,9 +270,13 @@ func NewConn(options *Options) (conn *Conn, err error) { wireguardEngine.SetStatusCallback(nodeUp.setStatus) wireguardEngine.SetNetInfoCallback(nodeUp.setNetInfo) magicConn.SetDERPForcedWebsocketCallback(nodeUp.setDERPForcedWebsocket) + if options.TelemetrySink != nil { + magicConn.SetNetInfoCallback(telemetryStore.setNetInfo) + } server := &Conn{ id: uuid.New(), + nodeID: nodeID, closed: make(chan struct{}), logger: logger, magicConn: magicConn, @@ -288,7 +292,7 @@ func NewConn(options *Options) (conn *Conn, err error) { configMaps: cfgMaps, nodeUpdater: nodeUp, telemetrySink: options.TelemetrySink, - telemetryLogs: telemetryLogSink, + telemeteryStore: telemetryStore, } defer func() { if err != nil { @@ -334,6 +338,7 @@ func IPFromUUID(uid uuid.UUID) netip.Addr { type Conn struct { // ID must be unique to this connection id uuid.UUID + nodeID tailcfg.NodeID mutex sync.Mutex closed chan struct{} logger multiLogger @@ -351,9 +356,9 @@ type Conn struct { clientType proto.TelemetryEvent_ClientType telemetrySink TelemetrySink - // telemetryLogs will be nil if telemetrySink is nil. - telemetryLogs *TelemetryStore - telemetryWg sync.WaitGroup + // telemeteryStore will be nil if telemetrySink is nil. + telemeteryStore *TelemetryStore + telemetryWg sync.WaitGroup trafficStats *connstats.Statistics } @@ -388,8 +393,8 @@ func (c *Conn) SetNodeCallback(callback func(node *Node)) { // SetDERPMap updates the DERPMap of a connection. func (c *Conn) SetDERPMap(derpMap *tailcfg.DERPMap) { - if c.configMaps.setDERPMap(derpMap) && c.telemetryLogs != nil { - c.telemetryLogs.updateDerpMap(derpMap) + if c.configMaps.setDERPMap(derpMap) && c.telemeteryStore != nil { + c.telemeteryStore.updateDerpMap(derpMap) } } @@ -729,19 +734,17 @@ func (c *Conn) newTelemetryEvent() (*proto.TelemetryEvent, error) { if err != nil { return nil, xerrors.Errorf("marshal uuid to bytes: %w", err) } - c.nodeUpdater.L.Lock() - node := c.nodeUpdater.nodeLocked() - c.nodeUpdater.L.Unlock() - logs, ips, dm := c.telemetryLogs.getStore() + logs, ips, dm, ni := c.telemeteryStore.getStore() return &proto.TelemetryEvent{ - Id: id, - Time: timestamppb.Now(), - ClientType: c.clientType, - NodeIdSelf: uint64(node.ID), - Logs: logs, - LogIpHashes: ips, - DerpMap: DERPMapToProto(dm), + Id: id, + Time: timestamppb.Now(), + ClientType: c.clientType, + NodeIdSelf: uint64(c.nodeID), + Logs: logs, + LogIpHashes: ips, + DerpMap: DERPMapToProto(dm), + LatestNetcheck: NetInfoToProto(ni), // TODO: Application: "", @@ -749,7 +752,6 @@ func (c *Conn) newTelemetryEvent() (*proto.TelemetryEvent, error) { P2PEndpoint: &proto.TelemetryEvent_P2PEndpoint{}, ThroughputMbits: &wrapperspb.FloatValue{}, HomeDerp: "", - LatestNetcheck: &proto.Netcheck{}, ConnectionAge: &durationpb.Duration{}, ConnectionSetup: &durationpb.Duration{}, P2PSetup: &durationpb.Duration{}, diff --git a/tailnet/convert.go b/tailnet/convert.go index a7d224dc01bd0..1b8b3daca8e01 100644 --- a/tailnet/convert.go +++ b/tailnet/convert.go @@ -270,3 +270,13 @@ func DERPNodeFromProto(node *proto.DERPMap_Region_Node) *tailcfg.DERPNode { CanPort80: node.CanPort_80, } } + +func NetInfoToProto(netInfo *tailcfg.NetInfo) *proto.Netcheck { + if netInfo == nil { + return nil + } + + return &proto.Netcheck{ + // TODO: + } +} diff --git a/tailnet/logger_internal_test.go b/tailnet/logger_internal_test.go index 81485fce26bd3..901cfc2f02a20 100644 --- a/tailnet/logger_internal_test.go +++ b/tailnet/logger_internal_test.go @@ -26,7 +26,7 @@ func TestTelemetryStore(t *testing.T) { logger.Debug(ctx, "line2 fe80") logger.Debug(ctx, "line3 xxxx::x") - logs, hashes, _ := sink.getStore() + logs, hashes, _, _ := sink.getStore() require.Len(t, logs, 3) require.Len(t, hashes, 0) require.Contains(t, logs[0], "line1") @@ -117,7 +117,7 @@ func TestTelemetryStore(t *testing.T) { logger.Debug(ctx, fmt.Sprintf("line2: %s/24", c.ip)) logger.Debug(ctx, fmt.Sprintf("line3: %s foo (%s)", ipWithPort, c.ip)) - logs, ips, _ := sink.getStore() + logs, ips, _, _ := sink.getStore() require.Len(t, logs, 3) require.Len(t, ips, 1) for _, log := range logs { @@ -179,7 +179,7 @@ func TestTelemetryStore(t *testing.T) { logger.Debug(ctx, "line2 1.2.3.4 asdf") logger.Debug(ctx, "line3 2001:db8::1 foo") - logs, ips, dm := telemetry.getStore() + logs, ips, dm, _ := telemetry.getStore() require.Len(t, logs, 3) require.Len(t, ips, 3) require.Len(t, dm.Regions[999].Nodes, 1) diff --git a/tailnet/telemetry.go b/tailnet/telemetry.go index 534bff627d80d..452908daae11a 100644 --- a/tailnet/telemetry.go +++ b/tailnet/telemetry.go @@ -101,6 +101,7 @@ type TelemetryStore struct { cleanDerpMap *tailcfg.DERPMap derpMapFilter *regexp.Regexp + netInfo *tailcfg.NetInfo } var _ slog.Sink = &TelemetryStore{} @@ -123,10 +124,12 @@ func newTelemetryStore() (*TelemetryStore, error) { return out, nil } -func (b *TelemetryStore) getStore() ([]string, map[string]*proto.IPFields, *tailcfg.DERPMap) { +// getStore returns a deep copy of all current telemetry state. +// TODO: Should this return a populated event instead? +func (b *TelemetryStore) getStore() ([]string, map[string]*proto.IPFields, *tailcfg.DERPMap, *tailcfg.NetInfo) { b.mu.Lock() defer b.mu.Unlock() - return append([]string{}, b.logs...), b.hashedIPs, b.cleanDerpMap.Clone() + return append([]string{}, b.logs...), b.hashedIPs, b.cleanDerpMap.Clone(), b.netInfo.Clone() } // Given a DERPMap, anonymise all IPs and hostnames. @@ -161,6 +164,13 @@ func (b *TelemetryStore) updateDerpMap(cur *tailcfg.DERPMap) { b.cleanDerpMap = cleanMap } +func (b *TelemetryStore) setNetInfo(ni *tailcfg.NetInfo) { + b.mu.Lock() + defer b.mu.Unlock() + // TODO: Scrub PII from NetInfo + b.netInfo = ni +} + // Write implements io.Writer. func (b *TelemetryStore) Write(p []byte) (n int, err error) { b.mu.Lock() From 5c0f30ca668682acbc94546c7baf43782f192bfc Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Mon, 1 Jul 2024 04:48:57 +0000 Subject: [PATCH 07/19] add netcheck --- tailnet/conn.go | 8 +++--- tailnet/convert.go | 48 ++++++++++++++++++++++++++++++++- tailnet/logger_internal_test.go | 12 ++++----- tailnet/telemetry.go | 7 +++-- 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/tailnet/conn.go b/tailnet/conn.go index 773bc656334f0..7584d599bf802 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -521,7 +521,7 @@ func (c *Conn) AwaitReachable(ctx context.Context, ip netip.Addr) bool { case <-completedCtx.Done(): // TODO(ethanndickson): For now, I'm interpreting 'connected' as when the // agent is reachable. - _ = c.connectedTelemetryEvent() + // _ = c.connectedTelemetryEvent() return true case <-t.C: // Pings can take a while, so we can run multiple @@ -735,7 +735,7 @@ func (c *Conn) newTelemetryEvent() (*proto.TelemetryEvent, error) { return nil, xerrors.Errorf("marshal uuid to bytes: %w", err) } - logs, ips, dm, ni := c.telemeteryStore.getStore() + logs, ips, dm, nc := c.telemeteryStore.getStore() return &proto.TelemetryEvent{ Id: id, Time: timestamppb.Now(), @@ -743,8 +743,8 @@ func (c *Conn) newTelemetryEvent() (*proto.TelemetryEvent, error) { NodeIdSelf: uint64(c.nodeID), Logs: logs, LogIpHashes: ips, - DerpMap: DERPMapToProto(dm), - LatestNetcheck: NetInfoToProto(ni), + DerpMap: dm, + LatestNetcheck: nc, // TODO: Application: "", diff --git a/tailnet/convert.go b/tailnet/convert.go index 1b8b3daca8e01..d8a0c29d16713 100644 --- a/tailnet/convert.go +++ b/tailnet/convert.go @@ -2,10 +2,15 @@ package tailnet import ( "net/netip" + "strconv" + "strings" + "time" "github.com/google/uuid" "golang.org/x/xerrors" + "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" "tailscale.com/tailcfg" "tailscale.com/types/key" @@ -275,8 +280,49 @@ func NetInfoToProto(netInfo *tailcfg.NetInfo) *proto.Netcheck { if netInfo == nil { return nil } + rlv4 := make(map[int64]*durationpb.Duration) + rlv6 := make(map[int64]*durationpb.Duration) + for k, seconds := range netInfo.DERPLatency { + res := strings.Split(k, "-") + if len(res) != 2 { + continue + } + rid, err := strconv.ParseInt(res[0], 10, 64) + if err != nil { + continue + } + + dur := time.Duration(seconds * float64(time.Second)) + + switch res[1] { + case "v4": + rlv4[rid] = durationpb.New(dur) + case "v6": + rlv6[rid] = durationpb.New(dur) + } + } return &proto.Netcheck{ - // TODO: + UDP: netInfo.WorkingUDP.EqualBool(true), + IPv6CanSend: netInfo.WorkingIPv6.EqualBool(true), + OSHasIPv6: netInfo.OSHasIPv6.EqualBool(true), + ICMPv4: netInfo.WorkingICMPv4.EqualBool(true), + MappingVariesByDestIP: wrapperspb.Bool(netInfo.MappingVariesByDestIP.EqualBool(true)), + HairPinning: wrapperspb.Bool(netInfo.HairPinning.EqualBool(true)), + UPnP: wrapperspb.Bool(netInfo.UPnP.EqualBool(true)), + PMP: wrapperspb.Bool(netInfo.PMP.EqualBool(true)), + PCP: wrapperspb.Bool(netInfo.PCP.EqualBool(true)), + PreferredDERP: int64(netInfo.PreferredDERP), + RegionV4Latency: rlv4, + RegionV6Latency: rlv6, + // TODO: what's the most useful value to have here? + RegionLatency: make(map[int64]*durationpb.Duration), + // TODO: None of these are exposed by tailscale + IPv4CanSend: false, + IPv4: false, + IPv6: false, + GlobalV4: &proto.Netcheck_NetcheckIP{}, + GlobalV6: &proto.Netcheck_NetcheckIP{}, + CaptivePortal: &wrapperspb.BoolValue{}, } } diff --git a/tailnet/logger_internal_test.go b/tailnet/logger_internal_test.go index 901cfc2f02a20..04cf43c318b26 100644 --- a/tailnet/logger_internal_test.go +++ b/tailnet/logger_internal_test.go @@ -185,12 +185,12 @@ func TestTelemetryStore(t *testing.T) { require.Len(t, dm.Regions[999].Nodes, 1) node := dm.Regions[999].Nodes[0] require.NotContains(t, node.HostName, "coolderp.com") - require.NotContains(t, node.IPv4, "1.2.3.4") - require.NotContains(t, node.IPv6, "2001:db8::1") - require.NotContains(t, node.STUNTestIP, "5.6.7.8") + require.NotContains(t, node.Ipv4, "1.2.3.4") + require.NotContains(t, node.Ipv6, "2001:db8::1") + require.NotContains(t, node.StunTestIp, "5.6.7.8") require.Contains(t, logs[0], node.HostName) - require.Contains(t, ips, node.STUNTestIP) - require.Contains(t, ips, node.IPv6) - require.Contains(t, ips, node.IPv4) + require.Contains(t, ips, node.StunTestIp) + require.Contains(t, ips, node.Ipv6) + require.Contains(t, ips, node.Ipv4) }) } diff --git a/tailnet/telemetry.go b/tailnet/telemetry.go index 452908daae11a..e98b1943f6a53 100644 --- a/tailnet/telemetry.go +++ b/tailnet/telemetry.go @@ -126,10 +126,10 @@ func newTelemetryStore() (*TelemetryStore, error) { // getStore returns a deep copy of all current telemetry state. // TODO: Should this return a populated event instead? -func (b *TelemetryStore) getStore() ([]string, map[string]*proto.IPFields, *tailcfg.DERPMap, *tailcfg.NetInfo) { +func (b *TelemetryStore) getStore() ([]string, map[string]*proto.IPFields, *proto.DERPMap, *proto.Netcheck) { b.mu.Lock() defer b.mu.Unlock() - return append([]string{}, b.logs...), b.hashedIPs, b.cleanDerpMap.Clone(), b.netInfo.Clone() + return append([]string{}, b.logs...), b.hashedIPs, DERPMapToProto(b.cleanDerpMap), NetInfoToProto(b.netInfo) } // Given a DERPMap, anonymise all IPs and hostnames. @@ -167,8 +167,7 @@ func (b *TelemetryStore) updateDerpMap(cur *tailcfg.DERPMap) { func (b *TelemetryStore) setNetInfo(ni *tailcfg.NetInfo) { b.mu.Lock() defer b.mu.Unlock() - // TODO: Scrub PII from NetInfo - b.netInfo = ni + b.netInfo = ni.Clone() } // Write implements io.Writer. From 45b5234e9ff0fe7416dd56473ff5ecea79341b01 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Mon, 1 Jul 2024 05:41:07 +0000 Subject: [PATCH 08/19] fixup --- tailnet/conn.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tailnet/conn.go b/tailnet/conn.go index 7584d599bf802..1d31fb13fb8a9 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -268,10 +268,14 @@ func NewConn(options *Options) (conn *Conn, err error) { nodeUp.setAddresses(options.Addresses) nodeUp.setBlockEndpoints(options.BlockEndpoints) wireguardEngine.SetStatusCallback(nodeUp.setStatus) - wireguardEngine.SetNetInfoCallback(nodeUp.setNetInfo) magicConn.SetDERPForcedWebsocketCallback(nodeUp.setDERPForcedWebsocket) - if options.TelemetrySink != nil { - magicConn.SetNetInfoCallback(telemetryStore.setNetInfo) + if telemetryStore != nil { + wireguardEngine.SetNetInfoCallback(func(ni *tailcfg.NetInfo) { + nodeUp.setNetInfo(ni) + telemetryStore.setNetInfo(ni) + }) + } else { + wireguardEngine.SetNetInfoCallback(nodeUp.setNetInfo) } server := &Conn{ From f83df79ae425ad560a3e083f96a206828645f809 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Mon, 1 Jul 2024 09:46:35 +0000 Subject: [PATCH 09/19] tests --- codersdk/workspacesdk/connector.go | 12 +- .../workspacesdk/connector_internal_test.go | 254 ++++++++++++++++++ tailnet/conn.go | 2 +- tailnet/telemetry.go | 4 +- ...nal_test.go => telemetry_internal_test.go} | 0 5 files changed, 265 insertions(+), 7 deletions(-) rename tailnet/{logger_internal_test.go => telemetry_internal_test.go} (100%) diff --git a/codersdk/workspacesdk/connector.go b/codersdk/workspacesdk/connector.go index b3c23350740a2..44bcd46699ac4 100644 --- a/codersdk/workspacesdk/connector.go +++ b/codersdk/workspacesdk/connector.go @@ -63,6 +63,7 @@ type tailnetAPIConnector struct { coordinateURL string dialOptions *websocket.DialOptions conn tailnetConn + customDialFn func() (proto.DRPCTailnetClient, error) clientMu sync.RWMutex client proto.DRPCTailnetClient @@ -71,9 +72,9 @@ type tailnetAPIConnector struct { isFirst bool closed chan struct{} - // Set to true if we get a response from the server that it doesn't support + // Only set to true if we get a response from the server that it doesn't support // network telemetry. - telemetryDisabled atomic.Bool + telemetryUnavailable atomic.Bool } // Create a new tailnetAPIConnector without running it @@ -133,6 +134,9 @@ var permanentErrorStatuses = []int{ } func (tac *tailnetAPIConnector) dial() (proto.DRPCTailnetClient, error) { + if tac.customDialFn != nil { + return tac.customDialFn() + } tac.logger.Debug(tac.ctx, "dialing Coder tailnet v2+ API") // nolint:bodyclose ws, res, err := websocket.Dial(tac.ctx, tac.coordinateURL, tac.dialOptions) @@ -277,7 +281,7 @@ func (tac *tailnetAPIConnector) SendTelemetryEvent(event *proto.TelemetryEvent) // We hold the lock for the entire telemetry request, but this would only block // a coordinate retry, and closing the connection. defer tac.clientMu.RUnlock() - if tac.client == nil || tac.telemetryDisabled.Load() { + if tac.client == nil || tac.telemetryUnavailable.Load() { return } ctx, cancel := context.WithTimeout(tac.ctx, 5*time.Second) @@ -287,6 +291,6 @@ func (tac *tailnetAPIConnector) SendTelemetryEvent(event *proto.TelemetryEvent) }) if drpcerr.Code(err) == drpcerr.Unimplemented || drpc.ProtocolError.Has(err) && strings.Contains(err.Error(), "unknown rpc: ") { tac.logger.Debug(tac.ctx, "attempted to send telemetry to a server that doesn't support it", slog.Error(err)) - tac.telemetryDisabled.Store(true) + tac.telemetryUnavailable.Store(true) } } diff --git a/codersdk/workspacesdk/connector_internal_test.go b/codersdk/workspacesdk/connector_internal_test.go index 845be132163d2..d41e61b03e35c 100644 --- a/codersdk/workspacesdk/connector_internal_test.go +++ b/codersdk/workspacesdk/connector_internal_test.go @@ -13,7 +13,10 @@ import ( "github.com/hashicorp/yamux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/xerrors" "nhooyr.io/websocket" + "storj.io/drpc" + "storj.io/drpc/drpcerr" "tailscale.com/tailcfg" "cdr.dev/slog" @@ -139,6 +142,140 @@ func TestTailnetAPIConnector_UplevelVersion(t *testing.T) { require.NotEmpty(t, sdkErr.Helper) } +func TestTailnetAPIConnector_TelemetrySuccess(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + agentID := uuid.UUID{0x55} + clientID := uuid.UUID{0x66} + fCoord := tailnettest.NewFakeCoordinator() + var coord tailnet.Coordinator = fCoord + coordPtr := atomic.Pointer[tailnet.Coordinator]{} + coordPtr.Store(&coord) + derpMapCh := make(chan *tailcfg.DERPMap) + defer close(derpMapCh) + eventCh := make(chan []*proto.TelemetryEvent, 1) + svc, err := tailnet.NewClientService(tailnet.ClientServiceOptions{ + Logger: logger, + CoordPtr: &coordPtr, + DERPMapUpdateFrequency: time.Millisecond, + DERPMapFn: func() *tailcfg.DERPMap { return <-derpMapCh }, + NetworkTelemetryHandler: func(batch []*proto.TelemetryEvent) { + eventCh <- batch + }, + }) + require.NoError(t, err) + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + sws, err := websocket.Accept(w, r, nil) + if !assert.NoError(t, err) { + return + } + ctx, nc := codersdk.WebsocketNetConn(r.Context(), sws, websocket.MessageBinary) + err = svc.ServeConnV2(ctx, nc, tailnet.StreamID{ + Name: "client", + ID: clientID, + Auth: tailnet.ClientCoordinateeAuth{AgentID: agentID}, + }) + assert.NoError(t, err) + })) + + fConn := newFakeTailnetConn() + + uut := newTailnetAPIConnector(ctx, logger, agentID, svr.URL, &websocket.DialOptions{}) + uut.runConnector(fConn) + require.Eventually(t, func() bool { + uut.clientMu.Lock() + defer uut.clientMu.Unlock() + return uut.client != nil + }, testutil.WaitShort, testutil.IntervalFast) + + uut.SendTelemetryEvent(&proto.TelemetryEvent{ + Id: []byte("test event"), + }) + + testEvents := testutil.RequireRecvCtx(ctx, t, eventCh) + + require.Len(t, testEvents, 1) + require.Equal(t, []byte("test event"), testEvents[0].Id) +} + +// Server doesn't support telemetry / server unimplemented telemetry + +func TestTailnetAPIConnector_TelemetryUnimplemented(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + agentID := uuid.UUID{0x55} + fConn := newFakeTailnetConn() + + fakeDRPCClient := newFakeDRPCClient() + uut := &tailnetAPIConnector{ + ctx: ctx, + logger: logger, + agentID: agentID, + coordinateURL: "", + dialOptions: &websocket.DialOptions{}, + conn: nil, + connected: make(chan error, 1), + closed: make(chan struct{}), + customDialFn: func() (proto.DRPCTailnetClient, error) { + return fakeDRPCClient, nil + }, + } + uut.runConnector(fConn) + require.Eventually(t, func() bool { + uut.clientMu.Lock() + defer uut.clientMu.Unlock() + return uut.client != nil + }, testutil.WaitShort, testutil.IntervalFast) + + fakeDRPCClient.telemeteryErorr = drpcerr.WithCode(xerrors.New("Unimplemented"), 0) + uut.SendTelemetryEvent(&proto.TelemetryEvent{}) + require.False(t, uut.telemetryUnavailable.Load()) + + fakeDRPCClient.telemeteryErorr = drpcerr.WithCode(xerrors.New("Unimplemented"), drpcerr.Unimplemented) + uut.SendTelemetryEvent(&proto.TelemetryEvent{}) + require.True(t, uut.telemetryUnavailable.Load()) +} + +func TestTailnetAPIConnector_TelemetryNotRecognised(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + agentID := uuid.UUID{0x55} + fConn := newFakeTailnetConn() + + fakeDRPCClient := newFakeDRPCClient() + uut := &tailnetAPIConnector{ + ctx: ctx, + logger: logger, + agentID: agentID, + coordinateURL: "", + dialOptions: &websocket.DialOptions{}, + conn: nil, + connected: make(chan error, 1), + closed: make(chan struct{}), + customDialFn: func() (proto.DRPCTailnetClient, error) { + return fakeDRPCClient, nil + }, + } + uut.runConnector(fConn) + require.Eventually(t, func() bool { + uut.clientMu.Lock() + defer uut.clientMu.Unlock() + return uut.client != nil + }, testutil.WaitShort, testutil.IntervalFast) + + fakeDRPCClient.telemeteryErorr = drpc.ProtocolError.New("Protocol Error") + uut.SendTelemetryEvent(&proto.TelemetryEvent{}) + require.False(t, uut.telemetryUnavailable.Load()) + + fakeDRPCClient.telemeteryErorr = drpc.ProtocolError.New("unknown rpc: /coder.tailnet.v2.Tailnet/PostTelemetry") + uut.SendTelemetryEvent(&proto.TelemetryEvent{}) + require.True(t, uut.telemetryUnavailable.Load()) +} + type fakeTailnetConn struct{} func (*fakeTailnetConn) UpdatePeers([]*proto.CoordinateResponse_PeerUpdate) error { @@ -157,3 +294,120 @@ func (*fakeTailnetConn) SetTunnelDestination(uuid.UUID) {} func newFakeTailnetConn() *fakeTailnetConn { return &fakeTailnetConn{} } + +type fakeDRPCClient struct { + telemeteryErorr error + fakeDRPPCMapStream +} + +var _ proto.DRPCTailnetClient = &fakeDRPCClient{} + +func newFakeDRPCClient() *fakeDRPCClient { + return &fakeDRPCClient{ + fakeDRPPCMapStream: fakeDRPPCMapStream{ + fakeDRPCStream: fakeDRPCStream{ + ch: make(chan struct{}), + }, + }, + } +} + +// Coordinate implements proto.DRPCTailnetClient. +func (f *fakeDRPCClient) Coordinate(_ context.Context) (proto.DRPCTailnet_CoordinateClient, error) { + return &f.fakeDRPCStream, nil +} + +// DRPCConn implements proto.DRPCTailnetClient. +func (*fakeDRPCClient) DRPCConn() drpc.Conn { + return &fakeDRPCConn{} +} + +// PostTelemetry implements proto.DRPCTailnetClient. +func (f *fakeDRPCClient) PostTelemetry(_ context.Context, in *proto.TelemetryRequest) (*proto.TelemetryResponse, error) { + return nil, f.telemeteryErorr +} + +// StreamDERPMaps implements proto.DRPCTailnetClient. +func (f *fakeDRPCClient) StreamDERPMaps(_ context.Context, _ *proto.StreamDERPMapsRequest) (proto.DRPCTailnet_StreamDERPMapsClient, error) { + return &f.fakeDRPPCMapStream, nil +} + +type fakeDRPCConn struct{} + +var _ drpc.Conn = &fakeDRPCConn{} + +// Close implements drpc.Conn. +func (*fakeDRPCConn) Close() error { + return nil +} + +// Closed implements drpc.Conn. +func (*fakeDRPCConn) Closed() <-chan struct{} { + return nil +} + +// Invoke implements drpc.Conn. +func (*fakeDRPCConn) Invoke(_ context.Context, _ string, _ drpc.Encoding, _ drpc.Message, _ drpc.Message) error { + return nil +} + +// NewStream implements drpc.Conn. +func (*fakeDRPCConn) NewStream(_ context.Context, _ string, _ drpc.Encoding) (drpc.Stream, error) { + return nil, nil +} + +type fakeDRPCStream struct { + ch chan struct{} +} + +var _ proto.DRPCTailnet_CoordinateClient = &fakeDRPCStream{} + +// Close implements proto.DRPCTailnet_CoordinateClient. +func (f *fakeDRPCStream) Close() error { + close(f.ch) + return nil +} + +// CloseSend implements proto.DRPCTailnet_CoordinateClient. +func (*fakeDRPCStream) CloseSend() error { + return nil +} + +// Context implements proto.DRPCTailnet_CoordinateClient. +func (*fakeDRPCStream) Context() context.Context { + return nil +} + +// MsgRecv implements proto.DRPCTailnet_CoordinateClient. +func (*fakeDRPCStream) MsgRecv(_ drpc.Message, _ drpc.Encoding) error { + return nil +} + +// MsgSend implements proto.DRPCTailnet_CoordinateClient. +func (*fakeDRPCStream) MsgSend(_ drpc.Message, _ drpc.Encoding) error { + return nil +} + +// Recv implements proto.DRPCTailnet_CoordinateClient. +func (f *fakeDRPCStream) Recv() (*proto.CoordinateResponse, error) { + <-f.ch + return &proto.CoordinateResponse{}, nil +} + +// Send implements proto.DRPCTailnet_CoordinateClient. +func (f *fakeDRPCStream) Send(*proto.CoordinateRequest) error { + <-f.ch + return nil +} + +type fakeDRPPCMapStream struct { + fakeDRPCStream +} + +var _ proto.DRPCTailnet_StreamDERPMapsClient = &fakeDRPPCMapStream{} + +// Recv implements proto.DRPCTailnet_StreamDERPMapsClient. +func (f *fakeDRPPCMapStream) Recv() (*proto.DERPMap, error) { + <-f.fakeDRPCStream.ch + return &proto.DERPMap{}, nil +} diff --git a/tailnet/conn.go b/tailnet/conn.go index 1d31fb13fb8a9..31b7b209d3e81 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -144,7 +144,7 @@ func NewConn(options *Options) (conn *Conn, err error) { var err error telemetryStore, err = newTelemetryStore() if err != nil { - return nil, xerrors.Errorf("create telemetry log sink: %w", err) + return nil, xerrors.Errorf("create telemetry log store: %w", err) } logger = logger.appendLogger(slog.Make(telemetryStore).Leveled(slog.LevelDebug)) } diff --git a/tailnet/telemetry.go b/tailnet/telemetry.go index e98b1943f6a53..7a5e0a22c63c4 100644 --- a/tailnet/telemetry.go +++ b/tailnet/telemetry.go @@ -86,8 +86,8 @@ func (m multiLogger) With(fields ...slog.Field) multiLogger { return multiLogger{loggers: loggers} } -// A logger sink that extracts (anonymized) IP addresses from logs for building -// network telemetry events +// Responsible for storing and anonymizing networking telemetry state. +// Implements slog.Sink and io.Writer to store logs from `tailscale`. type TelemetryStore struct { // Always self-referential sink slog.Sink diff --git a/tailnet/logger_internal_test.go b/tailnet/telemetry_internal_test.go similarity index 100% rename from tailnet/logger_internal_test.go rename to tailnet/telemetry_internal_test.go From b9cd8fb7e019b49e273b1efddb568a330bbd8878 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Mon, 1 Jul 2024 11:31:13 +0000 Subject: [PATCH 10/19] ping and speedtest telemetry --- cli/speedtest.go | 1 + coderd/telemetry/telemetry.go | 3 - .../workspacesdk/connector_internal_test.go | 2 +- tailnet/conn.go | 59 ++- tailnet/convert.go | 17 +- tailnet/proto/tailnet.pb.go | 466 ++++++++---------- tailnet/proto/tailnet.proto | 7 +- 7 files changed, 277 insertions(+), 278 deletions(-) diff --git a/cli/speedtest.go b/cli/speedtest.go index 9f23d0c58f780..5a80d38d1c9f2 100644 --- a/cli/speedtest.go +++ b/cli/speedtest.go @@ -186,6 +186,7 @@ func (r *RootCmd) speedtest() *serpent.Command { outputResult.Intervals[i] = interval } } + _ = conn.Conn.SendSpeedtestTelemetry(outputResult.Overall.ThroughputMbits) out, err := formatter.Format(inv.Context(), outputResult) if err != nil { return err diff --git a/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index 3692d6eb5cbee..8ecdf67fac5a9 100644 --- a/coderd/telemetry/telemetry.go +++ b/coderd/telemetry/telemetry.go @@ -1221,14 +1221,11 @@ func netcheckFromProto(proto *tailnetproto.Netcheck) Netcheck { PreferredDERP: proto.PreferredDERP, - RegionLatency: durationMapFromProto(proto.RegionLatency), RegionV4Latency: durationMapFromProto(proto.RegionV4Latency), RegionV6Latency: durationMapFromProto(proto.RegionV6Latency), GlobalV4: netcheckIPFromProto(proto.GlobalV4), GlobalV6: netcheckIPFromProto(proto.GlobalV6), - - CaptivePortal: protoBool(proto.CaptivePortal), } } diff --git a/codersdk/workspacesdk/connector_internal_test.go b/codersdk/workspacesdk/connector_internal_test.go index d41e61b03e35c..e128503b2ab29 100644 --- a/codersdk/workspacesdk/connector_internal_test.go +++ b/codersdk/workspacesdk/connector_internal_test.go @@ -323,7 +323,7 @@ func (*fakeDRPCClient) DRPCConn() drpc.Conn { } // PostTelemetry implements proto.DRPCTailnetClient. -func (f *fakeDRPCClient) PostTelemetry(_ context.Context, in *proto.TelemetryRequest) (*proto.TelemetryResponse, error) { +func (f *fakeDRPCClient) PostTelemetry(_ context.Context, _ *proto.TelemetryRequest) (*proto.TelemetryResponse, error) { return nil, f.telemeteryErorr } diff --git a/tailnet/conn.go b/tailnet/conn.go index 31b7b209d3e81..58554e479b9f1 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -448,7 +448,12 @@ func (c *Conn) Status() *ipnstate.Status { // Ping sends a ping to the Wireguard engine. // The bool returned is true if the ping was performed P2P. func (c *Conn) Ping(ctx context.Context, ip netip.Addr) (time.Duration, bool, *ipnstate.PingResult, error) { - return c.pingWithType(ctx, ip, tailcfg.PingDisco) + dur, p2p, pr, err := c.pingWithType(ctx, ip, tailcfg.PingDisco) + if err == nil { + // TODO(ethanndickson): Is this too often? + _ = c.sendPingTelemetry(dur, p2p) + } + return dur, p2p, pr, err } func (c *Conn) pingWithType(ctx context.Context, ip netip.Addr, pt tailcfg.PingType) (time.Duration, bool, *ipnstate.PingResult, error) { @@ -525,7 +530,7 @@ func (c *Conn) AwaitReachable(ctx context.Context, ip netip.Addr) bool { case <-completedCtx.Done(): // TODO(ethanndickson): For now, I'm interpreting 'connected' as when the // agent is reachable. - // _ = c.connectedTelemetryEvent() + _ = c.sendConnectedTelemetry() return true case <-t.C: // Pings can take a while, so we can run multiple @@ -715,7 +720,24 @@ func (c *Conn) MagicsockServeHTTPDebug(w http.ResponseWriter, r *http.Request) { c.magicConn.ServeHTTPDebug(w, r) } -func (c *Conn) connectedTelemetryEvent() error { +func (c *Conn) sendConnectedTelemetry() error { + if c.telemetrySink == nil { + return nil + } + e, err := c.newTelemetryEvent() + if err != nil { + return xerrors.Errorf("create telemetry event: %w", err) + } + e.Status = proto.TelemetryEvent_CONNECTED + c.telemetryWg.Add(1) + go func() { + defer c.telemetryWg.Done() + c.telemetrySink.SendTelemetryEvent(e) + }() + return nil +} + +func (c *Conn) SendSpeedtestTelemetry(throughputMbits float64) error { if c.telemetrySink == nil { return nil } @@ -724,6 +746,30 @@ func (c *Conn) connectedTelemetryEvent() error { return xerrors.Errorf("create telemetry event: %w", err) } e.Status = proto.TelemetryEvent_CONNECTED + e.ThroughputMbits = wrapperspb.Float(float32(throughputMbits)) + c.telemetryWg.Add(1) + go func() { + defer c.telemetryWg.Done() + c.telemetrySink.SendTelemetryEvent(e) + }() + return nil +} + +// nolint: revive +func (c *Conn) sendPingTelemetry(latency time.Duration, p2p bool) error { + if c.telemetrySink == nil { + return nil + } + e, err := c.newTelemetryEvent() + if err != nil { + return xerrors.Errorf("create telemetry event: %w", err) + } + e.Status = proto.TelemetryEvent_CONNECTED + if p2p { + e.P2PLatency = durationpb.New(latency) + } else { + e.DerpLatency = durationpb.New(latency) + } c.telemetryWg.Add(1) go func() { defer c.telemetryWg.Done() @@ -754,14 +800,11 @@ func (c *Conn) newTelemetryEvent() (*proto.TelemetryEvent, error) { Application: "", NodeIdRemote: 0, P2PEndpoint: &proto.TelemetryEvent_P2PEndpoint{}, - ThroughputMbits: &wrapperspb.FloatValue{}, HomeDerp: "", ConnectionAge: &durationpb.Duration{}, ConnectionSetup: &durationpb.Duration{}, - P2PSetup: &durationpb.Duration{}, - // TODO: One of these two - DerpLatency: &durationpb.Duration{}, - P2PLatency: &durationpb.Duration{}, + // TODO: We only calculate this in one place, do we really want it? + P2PSetup: &durationpb.Duration{}, }, nil } diff --git a/tailnet/convert.go b/tailnet/convert.go index d8a0c29d16713..73eed332d4eac 100644 --- a/tailnet/convert.go +++ b/tailnet/convert.go @@ -304,8 +304,8 @@ func NetInfoToProto(netInfo *tailcfg.NetInfo) *proto.Netcheck { } return &proto.Netcheck{ UDP: netInfo.WorkingUDP.EqualBool(true), - IPv6CanSend: netInfo.WorkingIPv6.EqualBool(true), OSHasIPv6: netInfo.OSHasIPv6.EqualBool(true), + IPv6: netInfo.WorkingIPv6.EqualBool(true), ICMPv4: netInfo.WorkingICMPv4.EqualBool(true), MappingVariesByDestIP: wrapperspb.Bool(netInfo.MappingVariesByDestIP.EqualBool(true)), HairPinning: wrapperspb.Bool(netInfo.HairPinning.EqualBool(true)), @@ -315,14 +315,11 @@ func NetInfoToProto(netInfo *tailcfg.NetInfo) *proto.Netcheck { PreferredDERP: int64(netInfo.PreferredDERP), RegionV4Latency: rlv4, RegionV6Latency: rlv6, - // TODO: what's the most useful value to have here? - RegionLatency: make(map[int64]*durationpb.Duration), - // TODO: None of these are exposed by tailscale - IPv4CanSend: false, - IPv4: false, - IPv6: false, - GlobalV4: &proto.Netcheck_NetcheckIP{}, - GlobalV6: &proto.Netcheck_NetcheckIP{}, - CaptivePortal: &wrapperspb.BoolValue{}, + // TODO: These aren't yet exposed by Tailscale + IPv6CanSend: false, + IPv4CanSend: false, + IPv4: false, + GlobalV4: &proto.Netcheck_NetcheckIP{}, + GlobalV6: &proto.Netcheck_NetcheckIP{}, } } diff --git a/tailnet/proto/tailnet.pb.go b/tailnet/proto/tailnet.pb.go index 1990c40938499..0a8cf56f20139 100644 --- a/tailnet/proto/tailnet.pb.go +++ b/tailnet/proto/tailnet.pb.go @@ -648,12 +648,10 @@ type Netcheck struct { PMP *wrapperspb.BoolValue `protobuf:"bytes,11,opt,name=PMP,proto3" json:"PMP,omitempty"` PCP *wrapperspb.BoolValue `protobuf:"bytes,12,opt,name=PCP,proto3" json:"PCP,omitempty"` PreferredDERP int64 `protobuf:"varint,13,opt,name=PreferredDERP,proto3" json:"PreferredDERP,omitempty"` // 0 for unknown - RegionLatency map[int64]*durationpb.Duration `protobuf:"bytes,14,rep,name=RegionLatency,proto3" json:"RegionLatency,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - RegionV4Latency map[int64]*durationpb.Duration `protobuf:"bytes,15,rep,name=RegionV4Latency,proto3" json:"RegionV4Latency,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - RegionV6Latency map[int64]*durationpb.Duration `protobuf:"bytes,16,rep,name=RegionV6Latency,proto3" json:"RegionV6Latency,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + RegionV4Latency map[int64]*durationpb.Duration `protobuf:"bytes,14,rep,name=RegionV4Latency,proto3" json:"RegionV4Latency,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + RegionV6Latency map[int64]*durationpb.Duration `protobuf:"bytes,15,rep,name=RegionV6Latency,proto3" json:"RegionV6Latency,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` GlobalV4 *Netcheck_NetcheckIP `protobuf:"bytes,17,opt,name=GlobalV4,proto3" json:"GlobalV4,omitempty"` GlobalV6 *Netcheck_NetcheckIP `protobuf:"bytes,18,opt,name=GlobalV6,proto3" json:"GlobalV6,omitempty"` - CaptivePortal *wrapperspb.BoolValue `protobuf:"bytes,19,opt,name=CaptivePortal,proto3" json:"CaptivePortal,omitempty"` } func (x *Netcheck) Reset() { @@ -779,13 +777,6 @@ func (x *Netcheck) GetPreferredDERP() int64 { return 0 } -func (x *Netcheck) GetRegionLatency() map[int64]*durationpb.Duration { - if x != nil { - return x.RegionLatency - } - return nil -} - func (x *Netcheck) GetRegionV4Latency() map[int64]*durationpb.Duration { if x != nil { return x.RegionV4Latency @@ -814,13 +805,6 @@ func (x *Netcheck) GetGlobalV6() *Netcheck_NetcheckIP { return nil } -func (x *Netcheck) GetCaptivePortal() *wrapperspb.BoolValue { - if x != nil { - return x.CaptivePortal - } - return nil -} - type TelemetryEvent struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1648,7 +1632,7 @@ type Netcheck_NetcheckIP struct { func (x *Netcheck_NetcheckIP) Reset() { *x = Netcheck_NetcheckIP{} if protoimpl.UnsafeEnabled { - mi := &file_tailnet_proto_tailnet_proto_msgTypes[25] + mi := &file_tailnet_proto_tailnet_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1661,7 +1645,7 @@ func (x *Netcheck_NetcheckIP) String() string { func (*Netcheck_NetcheckIP) ProtoMessage() {} func (x *Netcheck_NetcheckIP) ProtoReflect() protoreflect.Message { - mi := &file_tailnet_proto_tailnet_proto_msgTypes[25] + mi := &file_tailnet_proto_tailnet_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1674,7 +1658,7 @@ func (x *Netcheck_NetcheckIP) ProtoReflect() protoreflect.Message { // Deprecated: Use Netcheck_NetcheckIP.ProtoReflect.Descriptor instead. func (*Netcheck_NetcheckIP) Descriptor() ([]byte, []int) { - return file_tailnet_proto_tailnet_proto_rawDescGZIP(), []int{6, 3} + return file_tailnet_proto_tailnet_proto_rawDescGZIP(), []int{6, 2} } func (x *Netcheck_NetcheckIP) GetHash() string { @@ -1704,7 +1688,7 @@ type TelemetryEvent_P2PEndpoint struct { func (x *TelemetryEvent_P2PEndpoint) Reset() { *x = TelemetryEvent_P2PEndpoint{} if protoimpl.UnsafeEnabled { - mi := &file_tailnet_proto_tailnet_proto_msgTypes[26] + mi := &file_tailnet_proto_tailnet_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1717,7 +1701,7 @@ func (x *TelemetryEvent_P2PEndpoint) String() string { func (*TelemetryEvent_P2PEndpoint) ProtoMessage() {} func (x *TelemetryEvent_P2PEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_tailnet_proto_tailnet_proto_msgTypes[26] + mi := &file_tailnet_proto_tailnet_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1937,7 +1921,7 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x73, 0x73, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x4c, - 0x4f, 0x4f, 0x50, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x03, 0x22, 0xc4, 0x0a, 0x0a, 0x08, 0x4e, 0x65, + 0x4f, 0x4f, 0x50, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x03, 0x22, 0xd0, 0x08, 0x0a, 0x08, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x55, 0x44, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x49, 0x50, 0x76, 0x36, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x49, 0x50, 0x76, 0x36, 0x12, 0x12, 0x0a, 0x04, @@ -1969,175 +1953,159 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x50, 0x43, 0x50, 0x12, 0x24, 0x0a, 0x0d, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x44, 0x45, 0x52, 0x50, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x50, - 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x44, 0x45, 0x52, 0x50, 0x12, 0x53, 0x0a, 0x0d, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0e, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, - 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x0d, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, - 0x79, 0x12, 0x59, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, - 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, - 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, - 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x59, 0x0a, 0x0f, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, - 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, + 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x44, 0x45, 0x52, 0x50, 0x12, 0x59, 0x0a, 0x0f, + 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, + 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, - 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, - 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x56, 0x34, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, - 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x50, - 0x52, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x34, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x6c, - 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x36, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x49, 0x50, 0x52, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x36, 0x12, 0x40, 0x0a, - 0x0d, 0x43, 0x61, 0x70, 0x74, 0x69, 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x18, 0x13, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x52, 0x0d, 0x43, 0x61, 0x70, 0x74, 0x69, 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x1a, - 0x5b, 0x0a, 0x12, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5d, 0x0a, 0x14, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5d, 0x0a, 0x14, 0x52, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x54, 0x0a, 0x0a, 0x4e, 0x65, - 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x06, - 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, - 0x22, 0xff, 0x0a, 0x0a, 0x0e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, - 0x69, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, - 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, - 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x4c, 0x0a, 0x0b, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x5f, - 0x69, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x6e, - 0x6f, 0x64, 0x65, 0x49, 0x64, 0x53, 0x65, 0x6c, 0x66, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x6f, 0x64, - 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x12, - 0x4f, 0x0a, 0x0c, 0x70, 0x32, 0x70, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, - 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, - 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x32, 0x70, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x12, 0x55, 0x0a, 0x0d, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, - 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, - 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x6f, 0x67, 0x49, 0x70, 0x48, - 0x61, 0x73, 0x68, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x6c, 0x6f, 0x67, 0x49, - 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x6d, 0x65, 0x5f, - 0x64, 0x65, 0x72, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x6d, 0x65, - 0x44, 0x65, 0x72, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x34, 0x0a, 0x08, 0x64, 0x65, 0x72, 0x70, - 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, - 0x52, 0x50, 0x4d, 0x61, 0x70, 0x52, 0x07, 0x64, 0x65, 0x72, 0x70, 0x4d, 0x61, 0x70, 0x12, 0x43, - 0x0a, 0x0f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x6b, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, + 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, + 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x59, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x52, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, + 0x63, 0x79, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x34, 0x18, 0x11, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, + 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x50, 0x52, 0x08, 0x47, 0x6c, 0x6f, + 0x62, 0x61, 0x6c, 0x56, 0x34, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, + 0x36, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, - 0x65, 0x63, 0x6b, 0x52, 0x0e, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x4e, 0x65, 0x74, 0x63, 0x68, - 0x65, 0x63, 0x6b, 0x12, 0x40, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, + 0x65, 0x63, 0x6b, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x50, 0x52, 0x08, + 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x36, 0x1a, 0x5d, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5d, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x54, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x73, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0xff, 0x0a, 0x0a, + 0x0e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, + 0x20, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x13, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x4c, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, + 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x73, + 0x65, 0x6c, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x49, + 0x64, 0x53, 0x65, 0x6c, 0x66, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, + 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6e, + 0x6f, 0x64, 0x65, 0x49, 0x64, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x70, + 0x32, 0x70, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, + 0x0b, 0x70, 0x32, 0x70, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x55, 0x0a, 0x0d, + 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x0a, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, + 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x6f, 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x6c, 0x6f, 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, + 0x68, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x6d, 0x65, 0x5f, 0x64, 0x65, 0x72, 0x70, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x6d, 0x65, 0x44, 0x65, 0x72, 0x70, + 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, + 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x34, 0x0a, 0x08, 0x64, 0x65, 0x72, 0x70, 0x5f, 0x6d, 0x61, 0x70, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, + 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, + 0x70, 0x52, 0x07, 0x64, 0x65, 0x72, 0x70, 0x4d, 0x61, 0x70, 0x12, 0x43, 0x0a, 0x0f, 0x6c, 0x61, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, + 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, + 0x0e, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, + 0x40, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x67, + 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x67, + 0x65, 0x12, 0x44, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x73, 0x65, 0x74, 0x75, 0x70, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x41, 0x67, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x74, 0x75, 0x70, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, 0x36, 0x0a, 0x09, 0x70, - 0x32, 0x70, 0x5f, 0x73, 0x65, 0x74, 0x75, 0x70, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x70, 0x32, 0x70, 0x53, 0x65, - 0x74, 0x75, 0x70, 0x12, 0x3c, 0x0a, 0x0c, 0x64, 0x65, 0x72, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, - 0x6e, 0x63, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x72, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, - 0x79, 0x12, 0x3a, 0x0a, 0x0b, 0x70, 0x32, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, - 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x0a, 0x70, 0x32, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x46, 0x0a, - 0x10, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x62, 0x69, 0x74, - 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, - 0x4d, 0x62, 0x69, 0x74, 0x73, 0x1a, 0x69, 0x0a, 0x0b, 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x06, - 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, - 0x1a, 0x5a, 0x0a, 0x10, 0x4c, 0x6f, 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, - 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x29, 0x0a, 0x06, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, - 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, - 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x01, 0x22, 0x39, 0x0a, 0x0a, 0x43, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x4c, 0x49, 0x10, 0x00, 0x12, 0x09, - 0x0a, 0x05, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x44, - 0x45, 0x52, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x53, 0x50, 0x52, 0x4f, 0x58, 0x59, - 0x10, 0x03, 0x22, 0x4c, 0x0a, 0x10, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, - 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, - 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, - 0x22, 0x13, 0x0a, 0x11, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x98, 0x02, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6c, 0x6e, 0x65, - 0x74, 0x12, 0x58, 0x0a, 0x0d, 0x50, 0x6f, 0x73, 0x74, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, - 0x72, 0x79, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, 0x36, 0x0a, 0x09, 0x70, 0x32, 0x70, 0x5f, 0x73, + 0x65, 0x74, 0x75, 0x70, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x70, 0x32, 0x70, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, + 0x3c, 0x0a, 0x0c, 0x64, 0x65, 0x72, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, + 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x0b, 0x64, 0x65, 0x72, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x3a, 0x0a, + 0x0b, 0x70, 0x32, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x13, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x70, + 0x32, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x46, 0x0a, 0x10, 0x74, 0x68, 0x72, + 0x6f, 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x62, 0x69, 0x74, 0x73, 0x18, 0x14, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x52, 0x0f, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, 0x4d, 0x62, 0x69, 0x74, + 0x73, 0x1a, 0x69, 0x0a, 0x0b, 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x73, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x5a, 0x0a, 0x10, + 0x4c, 0x6f, 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x29, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, + 0x44, 0x10, 0x01, 0x22, 0x39, 0x0a, 0x0a, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x4c, 0x49, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x47, + 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x44, 0x45, 0x52, 0x44, 0x10, + 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x53, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x10, 0x03, 0x22, 0x4c, + 0x0a, 0x10, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, + 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x13, 0x0a, 0x11, + 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x32, 0x98, 0x02, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x12, 0x58, 0x0a, + 0x0d, 0x50, 0x6f, 0x73, 0x74, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x12, 0x22, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, - 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, - 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0e, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x27, 0x2e, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, + 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x30, 0x01, 0x12, + 0x5b, 0x0a, 0x0a, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, - 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, - 0x70, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, - 0x65, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, - 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, - 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, - 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x61, - 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, + 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, + 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2153,7 +2121,7 @@ func file_tailnet_proto_tailnet_proto_rawDescGZIP() []byte { } var file_tailnet_proto_tailnet_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_tailnet_proto_tailnet_proto_msgTypes = make([]protoimpl.MessageInfo, 28) +var file_tailnet_proto_tailnet_proto_msgTypes = make([]protoimpl.MessageInfo, 27) var file_tailnet_proto_tailnet_proto_goTypes = []interface{}{ (CoordinateResponse_PeerUpdate_Kind)(0), // 0: coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind (IPFields_IPClass)(0), // 1: coder.tailnet.v2.IPFields.IPClass @@ -2181,21 +2149,20 @@ var file_tailnet_proto_tailnet_proto_goTypes = []interface{}{ (*CoordinateRequest_Tunnel)(nil), // 23: coder.tailnet.v2.CoordinateRequest.Tunnel (*CoordinateRequest_ReadyForHandshake)(nil), // 24: coder.tailnet.v2.CoordinateRequest.ReadyForHandshake (*CoordinateResponse_PeerUpdate)(nil), // 25: coder.tailnet.v2.CoordinateResponse.PeerUpdate - nil, // 26: coder.tailnet.v2.Netcheck.RegionLatencyEntry - nil, // 27: coder.tailnet.v2.Netcheck.RegionV4LatencyEntry - nil, // 28: coder.tailnet.v2.Netcheck.RegionV6LatencyEntry - (*Netcheck_NetcheckIP)(nil), // 29: coder.tailnet.v2.Netcheck.NetcheckIP - (*TelemetryEvent_P2PEndpoint)(nil), // 30: coder.tailnet.v2.TelemetryEvent.P2PEndpoint - nil, // 31: coder.tailnet.v2.TelemetryEvent.LogIpHashesEntry - (*timestamppb.Timestamp)(nil), // 32: google.protobuf.Timestamp - (*wrapperspb.BoolValue)(nil), // 33: google.protobuf.BoolValue - (*durationpb.Duration)(nil), // 34: google.protobuf.Duration - (*wrapperspb.FloatValue)(nil), // 35: google.protobuf.FloatValue + nil, // 26: coder.tailnet.v2.Netcheck.RegionV4LatencyEntry + nil, // 27: coder.tailnet.v2.Netcheck.RegionV6LatencyEntry + (*Netcheck_NetcheckIP)(nil), // 28: coder.tailnet.v2.Netcheck.NetcheckIP + (*TelemetryEvent_P2PEndpoint)(nil), // 29: coder.tailnet.v2.TelemetryEvent.P2PEndpoint + nil, // 30: coder.tailnet.v2.TelemetryEvent.LogIpHashesEntry + (*timestamppb.Timestamp)(nil), // 31: google.protobuf.Timestamp + (*wrapperspb.BoolValue)(nil), // 32: google.protobuf.BoolValue + (*durationpb.Duration)(nil), // 33: google.protobuf.Duration + (*wrapperspb.FloatValue)(nil), // 34: google.protobuf.FloatValue } var file_tailnet_proto_tailnet_proto_depIdxs = []int32{ 14, // 0: coder.tailnet.v2.DERPMap.home_params:type_name -> coder.tailnet.v2.DERPMap.HomeParams 16, // 1: coder.tailnet.v2.DERPMap.regions:type_name -> coder.tailnet.v2.DERPMap.RegionsEntry - 32, // 2: coder.tailnet.v2.Node.as_of:type_name -> google.protobuf.Timestamp + 31, // 2: coder.tailnet.v2.Node.as_of:type_name -> google.protobuf.Timestamp 19, // 3: coder.tailnet.v2.Node.derp_latency:type_name -> coder.tailnet.v2.Node.DerpLatencyEntry 20, // 4: coder.tailnet.v2.Node.derp_forced_websocket:type_name -> coder.tailnet.v2.Node.DerpForcedWebsocketEntry 21, // 5: coder.tailnet.v2.CoordinateRequest.update_self:type_name -> coder.tailnet.v2.CoordinateRequest.UpdateSelf @@ -2205,54 +2172,51 @@ var file_tailnet_proto_tailnet_proto_depIdxs = []int32{ 24, // 9: coder.tailnet.v2.CoordinateRequest.ready_for_handshake:type_name -> coder.tailnet.v2.CoordinateRequest.ReadyForHandshake 25, // 10: coder.tailnet.v2.CoordinateResponse.peer_updates:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate 1, // 11: coder.tailnet.v2.IPFields.class:type_name -> coder.tailnet.v2.IPFields.IPClass - 33, // 12: coder.tailnet.v2.Netcheck.MappingVariesByDestIP:type_name -> google.protobuf.BoolValue - 33, // 13: coder.tailnet.v2.Netcheck.HairPinning:type_name -> google.protobuf.BoolValue - 33, // 14: coder.tailnet.v2.Netcheck.UPnP:type_name -> google.protobuf.BoolValue - 33, // 15: coder.tailnet.v2.Netcheck.PMP:type_name -> google.protobuf.BoolValue - 33, // 16: coder.tailnet.v2.Netcheck.PCP:type_name -> google.protobuf.BoolValue - 26, // 17: coder.tailnet.v2.Netcheck.RegionLatency:type_name -> coder.tailnet.v2.Netcheck.RegionLatencyEntry - 27, // 18: coder.tailnet.v2.Netcheck.RegionV4Latency:type_name -> coder.tailnet.v2.Netcheck.RegionV4LatencyEntry - 28, // 19: coder.tailnet.v2.Netcheck.RegionV6Latency:type_name -> coder.tailnet.v2.Netcheck.RegionV6LatencyEntry - 29, // 20: coder.tailnet.v2.Netcheck.GlobalV4:type_name -> coder.tailnet.v2.Netcheck.NetcheckIP - 29, // 21: coder.tailnet.v2.Netcheck.GlobalV6:type_name -> coder.tailnet.v2.Netcheck.NetcheckIP - 33, // 22: coder.tailnet.v2.Netcheck.CaptivePortal:type_name -> google.protobuf.BoolValue - 32, // 23: coder.tailnet.v2.TelemetryEvent.time:type_name -> google.protobuf.Timestamp - 2, // 24: coder.tailnet.v2.TelemetryEvent.status:type_name -> coder.tailnet.v2.TelemetryEvent.Status - 3, // 25: coder.tailnet.v2.TelemetryEvent.client_type:type_name -> coder.tailnet.v2.TelemetryEvent.ClientType - 30, // 26: coder.tailnet.v2.TelemetryEvent.p2p_endpoint:type_name -> coder.tailnet.v2.TelemetryEvent.P2PEndpoint - 31, // 27: coder.tailnet.v2.TelemetryEvent.log_ip_hashes:type_name -> coder.tailnet.v2.TelemetryEvent.LogIpHashesEntry - 4, // 28: coder.tailnet.v2.TelemetryEvent.derp_map:type_name -> coder.tailnet.v2.DERPMap - 10, // 29: coder.tailnet.v2.TelemetryEvent.latest_netcheck:type_name -> coder.tailnet.v2.Netcheck - 34, // 30: coder.tailnet.v2.TelemetryEvent.connection_age:type_name -> google.protobuf.Duration - 34, // 31: coder.tailnet.v2.TelemetryEvent.connection_setup:type_name -> google.protobuf.Duration - 34, // 32: coder.tailnet.v2.TelemetryEvent.p2p_setup:type_name -> google.protobuf.Duration - 34, // 33: coder.tailnet.v2.TelemetryEvent.derp_latency:type_name -> google.protobuf.Duration - 34, // 34: coder.tailnet.v2.TelemetryEvent.p2p_latency:type_name -> google.protobuf.Duration - 35, // 35: coder.tailnet.v2.TelemetryEvent.throughput_mbits:type_name -> google.protobuf.FloatValue - 11, // 36: coder.tailnet.v2.TelemetryRequest.events:type_name -> coder.tailnet.v2.TelemetryEvent - 17, // 37: coder.tailnet.v2.DERPMap.HomeParams.region_score:type_name -> coder.tailnet.v2.DERPMap.HomeParams.RegionScoreEntry - 18, // 38: coder.tailnet.v2.DERPMap.Region.nodes:type_name -> coder.tailnet.v2.DERPMap.Region.Node - 15, // 39: coder.tailnet.v2.DERPMap.RegionsEntry.value:type_name -> coder.tailnet.v2.DERPMap.Region - 6, // 40: coder.tailnet.v2.CoordinateRequest.UpdateSelf.node:type_name -> coder.tailnet.v2.Node - 6, // 41: coder.tailnet.v2.CoordinateResponse.PeerUpdate.node:type_name -> coder.tailnet.v2.Node - 0, // 42: coder.tailnet.v2.CoordinateResponse.PeerUpdate.kind:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind - 34, // 43: coder.tailnet.v2.Netcheck.RegionLatencyEntry.value:type_name -> google.protobuf.Duration - 34, // 44: coder.tailnet.v2.Netcheck.RegionV4LatencyEntry.value:type_name -> google.protobuf.Duration - 34, // 45: coder.tailnet.v2.Netcheck.RegionV6LatencyEntry.value:type_name -> google.protobuf.Duration - 9, // 46: coder.tailnet.v2.Netcheck.NetcheckIP.fields:type_name -> coder.tailnet.v2.IPFields - 9, // 47: coder.tailnet.v2.TelemetryEvent.P2PEndpoint.fields:type_name -> coder.tailnet.v2.IPFields - 9, // 48: coder.tailnet.v2.TelemetryEvent.LogIpHashesEntry.value:type_name -> coder.tailnet.v2.IPFields - 12, // 49: coder.tailnet.v2.Tailnet.PostTelemetry:input_type -> coder.tailnet.v2.TelemetryRequest - 5, // 50: coder.tailnet.v2.Tailnet.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest - 7, // 51: coder.tailnet.v2.Tailnet.Coordinate:input_type -> coder.tailnet.v2.CoordinateRequest - 13, // 52: coder.tailnet.v2.Tailnet.PostTelemetry:output_type -> coder.tailnet.v2.TelemetryResponse - 4, // 53: coder.tailnet.v2.Tailnet.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap - 8, // 54: coder.tailnet.v2.Tailnet.Coordinate:output_type -> coder.tailnet.v2.CoordinateResponse - 52, // [52:55] is the sub-list for method output_type - 49, // [49:52] is the sub-list for method input_type - 49, // [49:49] is the sub-list for extension type_name - 49, // [49:49] is the sub-list for extension extendee - 0, // [0:49] is the sub-list for field type_name + 32, // 12: coder.tailnet.v2.Netcheck.MappingVariesByDestIP:type_name -> google.protobuf.BoolValue + 32, // 13: coder.tailnet.v2.Netcheck.HairPinning:type_name -> google.protobuf.BoolValue + 32, // 14: coder.tailnet.v2.Netcheck.UPnP:type_name -> google.protobuf.BoolValue + 32, // 15: coder.tailnet.v2.Netcheck.PMP:type_name -> google.protobuf.BoolValue + 32, // 16: coder.tailnet.v2.Netcheck.PCP:type_name -> google.protobuf.BoolValue + 26, // 17: coder.tailnet.v2.Netcheck.RegionV4Latency:type_name -> coder.tailnet.v2.Netcheck.RegionV4LatencyEntry + 27, // 18: coder.tailnet.v2.Netcheck.RegionV6Latency:type_name -> coder.tailnet.v2.Netcheck.RegionV6LatencyEntry + 28, // 19: coder.tailnet.v2.Netcheck.GlobalV4:type_name -> coder.tailnet.v2.Netcheck.NetcheckIP + 28, // 20: coder.tailnet.v2.Netcheck.GlobalV6:type_name -> coder.tailnet.v2.Netcheck.NetcheckIP + 31, // 21: coder.tailnet.v2.TelemetryEvent.time:type_name -> google.protobuf.Timestamp + 2, // 22: coder.tailnet.v2.TelemetryEvent.status:type_name -> coder.tailnet.v2.TelemetryEvent.Status + 3, // 23: coder.tailnet.v2.TelemetryEvent.client_type:type_name -> coder.tailnet.v2.TelemetryEvent.ClientType + 29, // 24: coder.tailnet.v2.TelemetryEvent.p2p_endpoint:type_name -> coder.tailnet.v2.TelemetryEvent.P2PEndpoint + 30, // 25: coder.tailnet.v2.TelemetryEvent.log_ip_hashes:type_name -> coder.tailnet.v2.TelemetryEvent.LogIpHashesEntry + 4, // 26: coder.tailnet.v2.TelemetryEvent.derp_map:type_name -> coder.tailnet.v2.DERPMap + 10, // 27: coder.tailnet.v2.TelemetryEvent.latest_netcheck:type_name -> coder.tailnet.v2.Netcheck + 33, // 28: coder.tailnet.v2.TelemetryEvent.connection_age:type_name -> google.protobuf.Duration + 33, // 29: coder.tailnet.v2.TelemetryEvent.connection_setup:type_name -> google.protobuf.Duration + 33, // 30: coder.tailnet.v2.TelemetryEvent.p2p_setup:type_name -> google.protobuf.Duration + 33, // 31: coder.tailnet.v2.TelemetryEvent.derp_latency:type_name -> google.protobuf.Duration + 33, // 32: coder.tailnet.v2.TelemetryEvent.p2p_latency:type_name -> google.protobuf.Duration + 34, // 33: coder.tailnet.v2.TelemetryEvent.throughput_mbits:type_name -> google.protobuf.FloatValue + 11, // 34: coder.tailnet.v2.TelemetryRequest.events:type_name -> coder.tailnet.v2.TelemetryEvent + 17, // 35: coder.tailnet.v2.DERPMap.HomeParams.region_score:type_name -> coder.tailnet.v2.DERPMap.HomeParams.RegionScoreEntry + 18, // 36: coder.tailnet.v2.DERPMap.Region.nodes:type_name -> coder.tailnet.v2.DERPMap.Region.Node + 15, // 37: coder.tailnet.v2.DERPMap.RegionsEntry.value:type_name -> coder.tailnet.v2.DERPMap.Region + 6, // 38: coder.tailnet.v2.CoordinateRequest.UpdateSelf.node:type_name -> coder.tailnet.v2.Node + 6, // 39: coder.tailnet.v2.CoordinateResponse.PeerUpdate.node:type_name -> coder.tailnet.v2.Node + 0, // 40: coder.tailnet.v2.CoordinateResponse.PeerUpdate.kind:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind + 33, // 41: coder.tailnet.v2.Netcheck.RegionV4LatencyEntry.value:type_name -> google.protobuf.Duration + 33, // 42: coder.tailnet.v2.Netcheck.RegionV6LatencyEntry.value:type_name -> google.protobuf.Duration + 9, // 43: coder.tailnet.v2.Netcheck.NetcheckIP.fields:type_name -> coder.tailnet.v2.IPFields + 9, // 44: coder.tailnet.v2.TelemetryEvent.P2PEndpoint.fields:type_name -> coder.tailnet.v2.IPFields + 9, // 45: coder.tailnet.v2.TelemetryEvent.LogIpHashesEntry.value:type_name -> coder.tailnet.v2.IPFields + 12, // 46: coder.tailnet.v2.Tailnet.PostTelemetry:input_type -> coder.tailnet.v2.TelemetryRequest + 5, // 47: coder.tailnet.v2.Tailnet.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest + 7, // 48: coder.tailnet.v2.Tailnet.Coordinate:input_type -> coder.tailnet.v2.CoordinateRequest + 13, // 49: coder.tailnet.v2.Tailnet.PostTelemetry:output_type -> coder.tailnet.v2.TelemetryResponse + 4, // 50: coder.tailnet.v2.Tailnet.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap + 8, // 51: coder.tailnet.v2.Tailnet.Coordinate:output_type -> coder.tailnet.v2.CoordinateResponse + 49, // [49:52] is the sub-list for method output_type + 46, // [46:49] is the sub-list for method input_type + 46, // [46:46] is the sub-list for extension type_name + 46, // [46:46] is the sub-list for extension extendee + 0, // [0:46] is the sub-list for field type_name } func init() { file_tailnet_proto_tailnet_proto_init() } @@ -2477,7 +2441,7 @@ func file_tailnet_proto_tailnet_proto_init() { return nil } } - file_tailnet_proto_tailnet_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + file_tailnet_proto_tailnet_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Netcheck_NetcheckIP); i { case 0: return &v.state @@ -2489,7 +2453,7 @@ func file_tailnet_proto_tailnet_proto_init() { return nil } } - file_tailnet_proto_tailnet_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + file_tailnet_proto_tailnet_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TelemetryEvent_P2PEndpoint); i { case 0: return &v.state @@ -2508,7 +2472,7 @@ func file_tailnet_proto_tailnet_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_tailnet_proto_tailnet_proto_rawDesc, NumEnums: 4, - NumMessages: 28, + NumMessages: 27, NumExtensions: 0, NumServices: 1, }, diff --git a/tailnet/proto/tailnet.proto b/tailnet/proto/tailnet.proto index b96c1e7b3c997..720ef85281edb 100644 --- a/tailnet/proto/tailnet.proto +++ b/tailnet/proto/tailnet.proto @@ -130,9 +130,8 @@ message Netcheck { int64 PreferredDERP = 13; // 0 for unknown - map RegionLatency = 14; - map RegionV4Latency = 15; - map RegionV6Latency = 16; + map RegionV4Latency = 14; + map RegionV6Latency = 15; message NetcheckIP { string hash = 1; @@ -140,8 +139,6 @@ message Netcheck { } NetcheckIP GlobalV4 = 17; NetcheckIP GlobalV6 = 18; - - google.protobuf.BoolValue CaptivePortal = 19; } message TelemetryEvent { From ce0df7b12c0cc6c44e553b7e30976056f85d7357 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 2 Jul 2024 04:29:18 +0000 Subject: [PATCH 11/19] fixup --- cli/root.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/cli/root.go b/cli/root.go index 20818a7d716d5..f7310a14ebdf1 100644 --- a/cli/root.go +++ b/cli/root.go @@ -52,21 +52,6 @@ var ( ) const ( -<<<<<<< HEAD - varURL = "url" - varToken = "token" - varAgentToken = "agent-token" - varAgentTokenFile = "agent-token-file" - varAgentURL = "agent-url" - varHeader = "header" - varHeaderCommand = "header-command" - varNoOpen = "no-open" - varNoVersionCheck = "no-version-warning" - varNoFeatureWarning = "no-feature-warning" - varForceTty = "force-tty" - varVerbose = "verbose" - varDisableDirect = "disable-direct-connections" -======= varURL = "url" varToken = "token" varAgentToken = "agent-token" @@ -82,7 +67,6 @@ const ( varOrganizationSelect = "organization" varDisableDirect = "disable-direct-connections" varDisableNetworkTelemetry = "disable-network-telemetry" ->>>>>>> 365c3bc71 (chore: add DRPC tailnet & cli implementation) notLoggedInMessage = "You are not logged in. Try logging in using 'coder login '." From 598cad5266bd5939cbb7a71171b5be7658035eea Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 2 Jul 2024 06:54:07 +0000 Subject: [PATCH 12/19] bump tailscale, change api --- cli/root.go | 36 ++- cli/speedtest.go | 2 +- coderd/telemetry/telemetry.go | 4 +- flake.nix | 2 +- go.mod | 2 +- go.sum | 4 +- tailnet/conn.go | 63 +--- tailnet/convert.go | 53 ---- tailnet/proto/tailnet.pb.go | 451 +++++++++++++++-------------- tailnet/proto/tailnet.proto | 4 +- tailnet/telemetry.go | 89 +++++- tailnet/telemetry_internal_test.go | 48 +-- 12 files changed, 370 insertions(+), 388 deletions(-) diff --git a/cli/root.go b/cli/root.go index f7310a14ebdf1..82702162ae094 100644 --- a/cli/root.go +++ b/cli/root.go @@ -64,7 +64,6 @@ const ( varNoFeatureWarning = "no-feature-warning" varForceTty = "force-tty" varVerbose = "verbose" - varOrganizationSelect = "organization" varDisableDirect = "disable-direct-connections" varDisableNetworkTelemetry = "disable-network-telemetry" @@ -475,25 +474,24 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err // RootCmd contains parameters and helpers useful to all commands. type RootCmd struct { - clientURL *url.URL - token string - globalConfig string - header []string - headerCommand string - agentToken string - agentTokenFile string - agentURL *url.URL - forceTTY bool - noOpen bool - verbose bool - organizationSelect string - versionFlag bool - disableDirect bool - debugHTTP bool - noNetworkTelemetry bool + clientURL *url.URL + token string + globalConfig string + header []string + headerCommand string + agentToken string + agentTokenFile string + agentURL *url.URL + forceTTY bool + noOpen bool + verbose bool + versionFlag bool + disableDirect bool + debugHTTP bool - noVersionCheck bool - noFeatureWarning bool + noNetworkTelemetry bool + noVersionCheck bool + noFeatureWarning bool } // InitClient authenticates the client with files from disk diff --git a/cli/speedtest.go b/cli/speedtest.go index 5a80d38d1c9f2..72cb3f714c15c 100644 --- a/cli/speedtest.go +++ b/cli/speedtest.go @@ -186,7 +186,7 @@ func (r *RootCmd) speedtest() *serpent.Command { outputResult.Intervals[i] = interval } } - _ = conn.Conn.SendSpeedtestTelemetry(outputResult.Overall.ThroughputMbits) + conn.Conn.SendSpeedtestTelemetry(outputResult.Overall.ThroughputMbits) out, err := formatter.Format(inv.Context(), outputResult) if err != nil { return err diff --git a/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index 8ecdf67fac5a9..18733b8fce74c 100644 --- a/coderd/telemetry/telemetry.go +++ b/coderd/telemetry/telemetry.go @@ -1163,9 +1163,9 @@ type Netcheck struct { IPv4 bool `json:"ipv4"` IPv6CanSend bool `json:"ipv6_can_send"` IPv4CanSend bool `json:"ipv4_can_send"` - OSHasIPv6 bool `json:"os_has_ipv6"` ICMPv4 bool `json:"icmpv4"` + OSHasIPv6 *bool `json:"os_has_ipv6"` MappingVariesByDestIP *bool `json:"mapping_varies_by_dest_ip"` HairPinning *bool `json:"hair_pinning"` UPnP *bool `json:"upnp"` @@ -1210,9 +1210,9 @@ func netcheckFromProto(proto *tailnetproto.Netcheck) Netcheck { IPv4: proto.IPv4, IPv6CanSend: proto.IPv6CanSend, IPv4CanSend: proto.IPv4CanSend, - OSHasIPv6: proto.OSHasIPv6, ICMPv4: proto.ICMPv4, + OSHasIPv6: protoBool(proto.OSHasIPv6), MappingVariesByDestIP: protoBool(proto.MappingVariesByDestIP), HairPinning: protoBool(proto.HairPinning), UPnP: protoBool(proto.UPnP), diff --git a/flake.nix b/flake.nix index ee6fbca7bd923..087b65f630185 100644 --- a/flake.nix +++ b/flake.nix @@ -97,7 +97,7 @@ name = "coder-${osArch}"; # Updated with ./scripts/update-flake.sh`. # This should be updated whenever go.mod changes! - vendorHash = "sha256-e0L6osJwG0EF0M3TefxaAjDvN4jvQHxTGEUEECNO1Vw="; + vendorHash = "sha256-IbebvYg1E50elvojWbJICJiTzy5Y5bP7n57sGMENDFs="; proxyVendor = true; src = ./.; nativeBuildInputs = with pkgs; [ getopt openssl zstd ]; diff --git a/go.mod b/go.mod index 60953dfbb60d1..923736037cbab 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ replace github.com/dlclark/regexp2 => github.com/dlclark/regexp2 v1.7.0 // There are a few minor changes we make to Tailscale that we're slowly upstreaming. Compare here: // https://github.com/tailscale/tailscale/compare/main...coder:tailscale:main -replace tailscale.com => github.com/coder/tailscale v1.1.1-0.20240530071520-1ac63d3a4ee3 +replace tailscale.com => github.com/coder/tailscale v1.1.1-0.20240702054557-aa558fbe5374 // This is replaced to include // 1. a fix for a data race: c.f. https://github.com/tailscale/wireguard-go/pull/25 diff --git a/go.sum b/go.sum index 8fa7b0e737817..22db038ee3aca 100644 --- a/go.sum +++ b/go.sum @@ -213,8 +213,8 @@ github.com/coder/serpent v0.7.0 h1:zGpD2GlF3lKIVkMjNGKbkip88qzd5r/TRcc30X/SrT0= github.com/coder/serpent v0.7.0/go.mod h1:REkJ5ZFHQUWFTPLExhXYZ1CaHFjxvGNRlLXLdsI08YA= github.com/coder/ssh v0.0.0-20231128192721-70855dedb788 h1:YoUSJ19E8AtuUFVYBpXuOD6a/zVP3rcxezNsoDseTUw= github.com/coder/ssh v0.0.0-20231128192721-70855dedb788/go.mod h1:aGQbuCLyhRLMzZF067xc84Lh7JDs1FKwCmF1Crl9dxQ= -github.com/coder/tailscale v1.1.1-0.20240530071520-1ac63d3a4ee3 h1:F2QRxrwPJyMPmX5qU7UpwEenhsk9qDqHyvYFxON1RkI= -github.com/coder/tailscale v1.1.1-0.20240530071520-1ac63d3a4ee3/go.mod h1:rp6BIJxCp127/hvvDWNkHC9MxAlKvQfoOtBr8s5sCqo= +github.com/coder/tailscale v1.1.1-0.20240702054557-aa558fbe5374 h1:a5Eg7D5e2oAc0tN56ee4yxtiTo76ztpRlk6geljaZp8= +github.com/coder/tailscale v1.1.1-0.20240702054557-aa558fbe5374/go.mod h1:rp6BIJxCp127/hvvDWNkHC9MxAlKvQfoOtBr8s5sCqo= github.com/coder/terraform-provider-coder v0.23.0 h1:DuNLWxhnGlXyG0g+OCAZRI6xd8+bJjIEnE4F3hYgA4E= github.com/coder/terraform-provider-coder v0.23.0/go.mod h1:wMun9UZ9HT2CzF6qPPBup1odzBpVUc0/xSFoXgdI3tk= github.com/coder/wgtunnel v0.1.13-0.20240522110300-ade90dfb2da0 h1:C2/eCr+r0a5Auuw3YOiSyLNHkdMtyCZHPFBx7syN4rk= diff --git a/tailnet/conn.go b/tailnet/conn.go index 58554e479b9f1..4808db3d80db5 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -16,7 +16,6 @@ import ( "github.com/google/uuid" "golang.org/x/xerrors" "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" @@ -530,7 +529,7 @@ func (c *Conn) AwaitReachable(ctx context.Context, ip netip.Addr) bool { case <-completedCtx.Done(): // TODO(ethanndickson): For now, I'm interpreting 'connected' as when the // agent is reachable. - _ = c.sendConnectedTelemetry() + c.sendConnectedTelemetry() return true case <-t.C: // Pings can take a while, so we can run multiple @@ -720,31 +719,24 @@ func (c *Conn) MagicsockServeHTTPDebug(w http.ResponseWriter, r *http.Request) { c.magicConn.ServeHTTPDebug(w, r) } -func (c *Conn) sendConnectedTelemetry() error { +func (c *Conn) sendConnectedTelemetry() { if c.telemetrySink == nil { - return nil - } - e, err := c.newTelemetryEvent() - if err != nil { - return xerrors.Errorf("create telemetry event: %w", err) + return } + e := c.newTelemetryEvent() e.Status = proto.TelemetryEvent_CONNECTED c.telemetryWg.Add(1) go func() { defer c.telemetryWg.Done() c.telemetrySink.SendTelemetryEvent(e) }() - return nil } -func (c *Conn) SendSpeedtestTelemetry(throughputMbits float64) error { +func (c *Conn) SendSpeedtestTelemetry(throughputMbits float64) { if c.telemetrySink == nil { - return nil - } - e, err := c.newTelemetryEvent() - if err != nil { - return xerrors.Errorf("create telemetry event: %w", err) + return } + e := c.newTelemetryEvent() e.Status = proto.TelemetryEvent_CONNECTED e.ThroughputMbits = wrapperspb.Float(float32(throughputMbits)) c.telemetryWg.Add(1) @@ -752,7 +744,6 @@ func (c *Conn) SendSpeedtestTelemetry(throughputMbits float64) error { defer c.telemetryWg.Done() c.telemetrySink.SendTelemetryEvent(e) }() - return nil } // nolint: revive @@ -760,10 +751,7 @@ func (c *Conn) sendPingTelemetry(latency time.Duration, p2p bool) error { if c.telemetrySink == nil { return nil } - e, err := c.newTelemetryEvent() - if err != nil { - return xerrors.Errorf("create telemetry event: %w", err) - } + e := c.newTelemetryEvent() e.Status = proto.TelemetryEvent_CONNECTED if p2p { e.P2PLatency = durationpb.New(latency) @@ -779,33 +767,14 @@ func (c *Conn) sendPingTelemetry(latency time.Duration, p2p bool) error { } // The returned telemetry event will not have it's status set. -func (c *Conn) newTelemetryEvent() (*proto.TelemetryEvent, error) { - id, err := c.id.MarshalBinary() - if err != nil { - return nil, xerrors.Errorf("marshal uuid to bytes: %w", err) - } - - logs, ips, dm, nc := c.telemeteryStore.getStore() - return &proto.TelemetryEvent{ - Id: id, - Time: timestamppb.Now(), - ClientType: c.clientType, - NodeIdSelf: uint64(c.nodeID), - Logs: logs, - LogIpHashes: ips, - DerpMap: dm, - LatestNetcheck: nc, - - // TODO: - Application: "", - NodeIdRemote: 0, - P2PEndpoint: &proto.TelemetryEvent_P2PEndpoint{}, - HomeDerp: "", - ConnectionAge: &durationpb.Duration{}, - ConnectionSetup: &durationpb.Duration{}, - // TODO: We only calculate this in one place, do we really want it? - P2PSetup: &durationpb.Duration{}, - }, nil +func (c *Conn) newTelemetryEvent() *proto.TelemetryEvent { + // Infallible + id, _ := c.id.MarshalBinary() + event := c.telemeteryStore.getStore() + event.ClientType = c.clientType + event.Id = id + event.NodeIdSelf = uint64(c.nodeID) + return event } // PeerDiagnostics is a checklist of human-readable conditions necessary to establish an encrypted diff --git a/tailnet/convert.go b/tailnet/convert.go index 73eed332d4eac..a7d224dc01bd0 100644 --- a/tailnet/convert.go +++ b/tailnet/convert.go @@ -2,15 +2,10 @@ package tailnet import ( "net/netip" - "strconv" - "strings" - "time" "github.com/google/uuid" "golang.org/x/xerrors" - "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" - "google.golang.org/protobuf/types/known/wrapperspb" "tailscale.com/tailcfg" "tailscale.com/types/key" @@ -275,51 +270,3 @@ func DERPNodeFromProto(node *proto.DERPMap_Region_Node) *tailcfg.DERPNode { CanPort80: node.CanPort_80, } } - -func NetInfoToProto(netInfo *tailcfg.NetInfo) *proto.Netcheck { - if netInfo == nil { - return nil - } - rlv4 := make(map[int64]*durationpb.Duration) - rlv6 := make(map[int64]*durationpb.Duration) - for k, seconds := range netInfo.DERPLatency { - res := strings.Split(k, "-") - if len(res) != 2 { - continue - } - - rid, err := strconv.ParseInt(res[0], 10, 64) - if err != nil { - continue - } - - dur := time.Duration(seconds * float64(time.Second)) - - switch res[1] { - case "v4": - rlv4[rid] = durationpb.New(dur) - case "v6": - rlv6[rid] = durationpb.New(dur) - } - } - return &proto.Netcheck{ - UDP: netInfo.WorkingUDP.EqualBool(true), - OSHasIPv6: netInfo.OSHasIPv6.EqualBool(true), - IPv6: netInfo.WorkingIPv6.EqualBool(true), - ICMPv4: netInfo.WorkingICMPv4.EqualBool(true), - MappingVariesByDestIP: wrapperspb.Bool(netInfo.MappingVariesByDestIP.EqualBool(true)), - HairPinning: wrapperspb.Bool(netInfo.HairPinning.EqualBool(true)), - UPnP: wrapperspb.Bool(netInfo.UPnP.EqualBool(true)), - PMP: wrapperspb.Bool(netInfo.PMP.EqualBool(true)), - PCP: wrapperspb.Bool(netInfo.PCP.EqualBool(true)), - PreferredDERP: int64(netInfo.PreferredDERP), - RegionV4Latency: rlv4, - RegionV6Latency: rlv6, - // TODO: These aren't yet exposed by Tailscale - IPv6CanSend: false, - IPv4CanSend: false, - IPv4: false, - GlobalV4: &proto.Netcheck_NetcheckIP{}, - GlobalV6: &proto.Netcheck_NetcheckIP{}, - } -} diff --git a/tailnet/proto/tailnet.pb.go b/tailnet/proto/tailnet.pb.go index 0a8cf56f20139..1993b4bfee3cb 100644 --- a/tailnet/proto/tailnet.pb.go +++ b/tailnet/proto/tailnet.pb.go @@ -640,8 +640,8 @@ type Netcheck struct { IPv4 bool `protobuf:"varint,3,opt,name=IPv4,proto3" json:"IPv4,omitempty"` IPv6CanSend bool `protobuf:"varint,4,opt,name=IPv6CanSend,proto3" json:"IPv6CanSend,omitempty"` IPv4CanSend bool `protobuf:"varint,5,opt,name=IPv4CanSend,proto3" json:"IPv4CanSend,omitempty"` - OSHasIPv6 bool `protobuf:"varint,6,opt,name=OSHasIPv6,proto3" json:"OSHasIPv6,omitempty"` ICMPv4 bool `protobuf:"varint,7,opt,name=ICMPv4,proto3" json:"ICMPv4,omitempty"` + OSHasIPv6 *wrapperspb.BoolValue `protobuf:"bytes,6,opt,name=OSHasIPv6,proto3" json:"OSHasIPv6,omitempty"` MappingVariesByDestIP *wrapperspb.BoolValue `protobuf:"bytes,8,opt,name=MappingVariesByDestIP,proto3" json:"MappingVariesByDestIP,omitempty"` HairPinning *wrapperspb.BoolValue `protobuf:"bytes,9,opt,name=HairPinning,proto3" json:"HairPinning,omitempty"` UPnP *wrapperspb.BoolValue `protobuf:"bytes,10,opt,name=UPnP,proto3" json:"UPnP,omitempty"` @@ -721,18 +721,18 @@ func (x *Netcheck) GetIPv4CanSend() bool { return false } -func (x *Netcheck) GetOSHasIPv6() bool { +func (x *Netcheck) GetICMPv4() bool { if x != nil { - return x.OSHasIPv6 + return x.ICMPv4 } return false } -func (x *Netcheck) GetICMPv4() bool { +func (x *Netcheck) GetOSHasIPv6() *wrapperspb.BoolValue { if x != nil { - return x.ICMPv4 + return x.OSHasIPv6 } - return false + return nil } func (x *Netcheck) GetMappingVariesByDestIP() *wrapperspb.BoolValue { @@ -1921,7 +1921,7 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x73, 0x73, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x4c, - 0x4f, 0x4f, 0x50, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x03, 0x22, 0xd0, 0x08, 0x0a, 0x08, 0x4e, 0x65, + 0x4f, 0x4f, 0x50, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x03, 0x22, 0xec, 0x08, 0x0a, 0x08, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x55, 0x44, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x49, 0x50, 0x76, 0x36, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x49, 0x50, 0x76, 0x36, 0x12, 0x12, 0x0a, 0x04, @@ -1930,182 +1930,184 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x49, 0x50, 0x76, 0x36, 0x43, 0x61, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x49, 0x50, 0x76, 0x34, 0x43, 0x61, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x49, 0x50, 0x76, 0x34, 0x43, 0x61, 0x6e, - 0x53, 0x65, 0x6e, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x4f, 0x53, 0x48, 0x61, 0x73, 0x49, 0x50, 0x76, - 0x36, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x4f, 0x53, 0x48, 0x61, 0x73, 0x49, 0x50, - 0x76, 0x36, 0x12, 0x16, 0x0a, 0x06, 0x49, 0x43, 0x4d, 0x50, 0x76, 0x34, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x06, 0x49, 0x43, 0x4d, 0x50, 0x76, 0x34, 0x12, 0x50, 0x0a, 0x15, 0x4d, 0x61, - 0x70, 0x70, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x72, 0x69, 0x65, 0x73, 0x42, 0x79, 0x44, 0x65, 0x73, - 0x74, 0x49, 0x50, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x15, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x56, 0x61, - 0x72, 0x69, 0x65, 0x73, 0x42, 0x79, 0x44, 0x65, 0x73, 0x74, 0x49, 0x50, 0x12, 0x3c, 0x0a, 0x0b, - 0x48, 0x61, 0x69, 0x72, 0x50, 0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0b, 0x48, - 0x61, 0x69, 0x72, 0x50, 0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x2e, 0x0a, 0x04, 0x55, 0x50, - 0x6e, 0x50, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x55, 0x50, 0x6e, 0x50, 0x12, 0x2c, 0x0a, 0x03, 0x50, 0x4d, - 0x50, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x52, 0x03, 0x50, 0x4d, 0x50, 0x12, 0x2c, 0x0a, 0x03, 0x50, 0x43, 0x50, 0x18, - 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x53, 0x65, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x49, 0x43, 0x4d, 0x50, 0x76, 0x34, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x49, 0x43, 0x4d, 0x50, 0x76, 0x34, 0x12, 0x38, 0x0a, 0x09, + 0x4f, 0x53, 0x48, 0x61, 0x73, 0x49, 0x50, 0x76, 0x36, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x09, 0x4f, 0x53, 0x48, + 0x61, 0x73, 0x49, 0x50, 0x76, 0x36, 0x12, 0x50, 0x0a, 0x15, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, + 0x67, 0x56, 0x61, 0x72, 0x69, 0x65, 0x73, 0x42, 0x79, 0x44, 0x65, 0x73, 0x74, 0x49, 0x50, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x52, 0x03, 0x50, 0x43, 0x50, 0x12, 0x24, 0x0a, 0x0d, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, - 0x72, 0x65, 0x64, 0x44, 0x45, 0x52, 0x50, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x50, - 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x44, 0x45, 0x52, 0x50, 0x12, 0x59, 0x0a, 0x0f, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, - 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, - 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, - 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, - 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x59, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, - 0x63, 0x79, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x34, 0x18, 0x11, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, - 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, - 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x50, 0x52, 0x08, 0x47, 0x6c, 0x6f, - 0x62, 0x61, 0x6c, 0x56, 0x34, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, - 0x36, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, - 0x65, 0x63, 0x6b, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x50, 0x52, 0x08, - 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x36, 0x1a, 0x5d, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, - 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5d, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x54, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, - 0x63, 0x6b, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, - 0x65, 0x6c, 0x64, 0x73, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0xff, 0x0a, 0x0a, - 0x0e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x65, 0x52, 0x15, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x72, 0x69, 0x65, 0x73, + 0x42, 0x79, 0x44, 0x65, 0x73, 0x74, 0x49, 0x50, 0x12, 0x3c, 0x0a, 0x0b, 0x48, 0x61, 0x69, 0x72, + 0x50, 0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, - 0x20, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x13, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x4c, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, - 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x73, - 0x65, 0x6c, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x49, - 0x64, 0x53, 0x65, 0x6c, 0x66, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, - 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6e, - 0x6f, 0x64, 0x65, 0x49, 0x64, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x70, - 0x32, 0x70, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, - 0x0b, 0x70, 0x32, 0x70, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x55, 0x0a, 0x0d, - 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x0a, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, - 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x6f, 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x6c, 0x6f, 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, - 0x68, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x6d, 0x65, 0x5f, 0x64, 0x65, 0x72, 0x70, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x6d, 0x65, 0x44, 0x65, 0x72, 0x70, - 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, - 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x34, 0x0a, 0x08, 0x64, 0x65, 0x72, 0x70, 0x5f, 0x6d, 0x61, 0x70, - 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, - 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, - 0x70, 0x52, 0x07, 0x64, 0x65, 0x72, 0x70, 0x4d, 0x61, 0x70, 0x12, 0x43, 0x0a, 0x0f, 0x6c, 0x61, - 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, - 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, - 0x0e, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, - 0x40, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x67, - 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x67, - 0x65, 0x12, 0x44, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x73, 0x65, 0x74, 0x75, 0x70, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, 0x36, 0x0a, 0x09, 0x70, 0x32, 0x70, 0x5f, 0x73, - 0x65, 0x74, 0x75, 0x70, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x70, 0x32, 0x70, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, - 0x3c, 0x0a, 0x0c, 0x64, 0x65, 0x72, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, - 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x0b, 0x64, 0x65, 0x72, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x3a, 0x0a, - 0x0b, 0x70, 0x32, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x13, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x70, - 0x32, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x46, 0x0a, 0x10, 0x74, 0x68, 0x72, - 0x6f, 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x62, 0x69, 0x74, 0x73, 0x18, 0x14, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x52, 0x0f, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, 0x4d, 0x62, 0x69, 0x74, - 0x73, 0x1a, 0x69, 0x0a, 0x0b, 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0b, 0x48, 0x61, 0x69, 0x72, 0x50, + 0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x2e, 0x0a, 0x04, 0x55, 0x50, 0x6e, 0x50, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x52, 0x04, 0x55, 0x50, 0x6e, 0x50, 0x12, 0x2c, 0x0a, 0x03, 0x50, 0x4d, 0x50, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, + 0x03, 0x50, 0x4d, 0x50, 0x12, 0x2c, 0x0a, 0x03, 0x50, 0x43, 0x50, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x50, + 0x43, 0x50, 0x12, 0x24, 0x0a, 0x0d, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x44, + 0x45, 0x52, 0x50, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x50, 0x72, 0x65, 0x66, 0x65, + 0x72, 0x72, 0x65, 0x64, 0x44, 0x45, 0x52, 0x50, 0x12, 0x59, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0e, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x52, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, 0x4c, 0x61, 0x74, 0x65, + 0x6e, 0x63, 0x79, 0x12, 0x59, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, + 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, + 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x52, + 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x41, + 0x0a, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x34, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, 0x4e, 0x65, 0x74, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x50, 0x52, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, + 0x34, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x56, 0x36, 0x18, 0x12, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, + 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2e, + 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x50, 0x52, 0x08, 0x47, 0x6c, 0x6f, 0x62, + 0x61, 0x6c, 0x56, 0x36, 0x1a, 0x5d, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x34, + 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x1a, 0x5d, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x56, 0x36, 0x4c, + 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x1a, 0x54, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x68, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, - 0x65, 0x6c, 0x64, 0x73, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x5a, 0x0a, 0x10, - 0x4c, 0x6f, 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x29, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, - 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, - 0x44, 0x10, 0x01, 0x22, 0x39, 0x0a, 0x0a, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x4c, 0x49, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x47, - 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x44, 0x45, 0x52, 0x44, 0x10, - 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x53, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x10, 0x03, 0x22, 0x4c, - 0x0a, 0x10, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, - 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x13, 0x0a, 0x11, - 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x32, 0x98, 0x02, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x12, 0x58, 0x0a, - 0x0d, 0x50, 0x6f, 0x73, 0x74, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x12, 0x22, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, - 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, - 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x30, 0x01, 0x12, - 0x5b, 0x0a, 0x0a, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, + 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, + 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0xff, 0x0a, 0x0a, 0x0e, 0x54, 0x65, 0x6c, + 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x61, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3f, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, - 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, - 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x31, + 0x0a, 0x14, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x64, 0x69, + 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x12, 0x4c, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, + 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, + 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x20, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x53, 0x65, 0x6c, + 0x66, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x49, + 0x64, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x70, 0x32, 0x70, 0x5f, 0x65, + 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, + 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x32, 0x70, + 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x55, 0x0a, 0x0d, 0x6c, 0x6f, 0x67, 0x5f, + 0x69, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x31, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x2e, 0x4c, 0x6f, 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x0b, 0x6c, 0x6f, 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, + 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x6d, 0x65, 0x5f, 0x64, 0x65, 0x72, 0x70, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x6d, 0x65, 0x44, 0x65, 0x72, 0x70, 0x12, 0x12, 0x0a, 0x04, + 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, + 0x12, 0x34, 0x0a, 0x08, 0x64, 0x65, 0x72, 0x70, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, + 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x52, 0x07, 0x64, + 0x65, 0x72, 0x70, 0x4d, 0x61, 0x70, 0x12, 0x43, 0x0a, 0x0f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, + 0x5f, 0x6e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0e, 0x6c, 0x61, 0x74, + 0x65, 0x73, 0x74, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x40, 0x0a, 0x0e, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x0f, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x67, 0x65, 0x12, 0x44, 0x0a, + 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x74, 0x75, + 0x70, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, + 0x74, 0x75, 0x70, 0x12, 0x36, 0x0a, 0x09, 0x70, 0x32, 0x70, 0x5f, 0x73, 0x65, 0x74, 0x75, 0x70, + 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x08, 0x70, 0x32, 0x70, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, 0x3c, 0x0a, 0x0c, 0x64, + 0x65, 0x72, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, + 0x72, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x3a, 0x0a, 0x0b, 0x70, 0x32, 0x70, + 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x70, 0x32, 0x70, 0x4c, 0x61, + 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x46, 0x0a, 0x10, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, + 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x62, 0x69, 0x74, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x74, 0x68, + 0x72, 0x6f, 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, 0x4d, 0x62, 0x69, 0x74, 0x73, 0x1a, 0x69, 0x0a, + 0x0b, 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, + 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, + 0x70, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, + 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, + 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x5a, 0x0a, 0x10, 0x4c, 0x6f, 0x67, 0x49, + 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0x29, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, + 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, + 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x01, 0x22, + 0x39, 0x0a, 0x0a, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, + 0x03, 0x43, 0x4c, 0x49, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, + 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x44, 0x45, 0x52, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, + 0x07, 0x57, 0x53, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x10, 0x03, 0x22, 0x4c, 0x0a, 0x10, 0x54, 0x65, + 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, + 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x54, 0x65, 0x6c, 0x65, + 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x98, 0x02, + 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x12, 0x58, 0x0a, 0x0d, 0x50, 0x6f, 0x73, + 0x74, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, + 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, + 0x50, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, + 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, + 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x43, + 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, + 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2172,51 +2174,52 @@ var file_tailnet_proto_tailnet_proto_depIdxs = []int32{ 24, // 9: coder.tailnet.v2.CoordinateRequest.ready_for_handshake:type_name -> coder.tailnet.v2.CoordinateRequest.ReadyForHandshake 25, // 10: coder.tailnet.v2.CoordinateResponse.peer_updates:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate 1, // 11: coder.tailnet.v2.IPFields.class:type_name -> coder.tailnet.v2.IPFields.IPClass - 32, // 12: coder.tailnet.v2.Netcheck.MappingVariesByDestIP:type_name -> google.protobuf.BoolValue - 32, // 13: coder.tailnet.v2.Netcheck.HairPinning:type_name -> google.protobuf.BoolValue - 32, // 14: coder.tailnet.v2.Netcheck.UPnP:type_name -> google.protobuf.BoolValue - 32, // 15: coder.tailnet.v2.Netcheck.PMP:type_name -> google.protobuf.BoolValue - 32, // 16: coder.tailnet.v2.Netcheck.PCP:type_name -> google.protobuf.BoolValue - 26, // 17: coder.tailnet.v2.Netcheck.RegionV4Latency:type_name -> coder.tailnet.v2.Netcheck.RegionV4LatencyEntry - 27, // 18: coder.tailnet.v2.Netcheck.RegionV6Latency:type_name -> coder.tailnet.v2.Netcheck.RegionV6LatencyEntry - 28, // 19: coder.tailnet.v2.Netcheck.GlobalV4:type_name -> coder.tailnet.v2.Netcheck.NetcheckIP - 28, // 20: coder.tailnet.v2.Netcheck.GlobalV6:type_name -> coder.tailnet.v2.Netcheck.NetcheckIP - 31, // 21: coder.tailnet.v2.TelemetryEvent.time:type_name -> google.protobuf.Timestamp - 2, // 22: coder.tailnet.v2.TelemetryEvent.status:type_name -> coder.tailnet.v2.TelemetryEvent.Status - 3, // 23: coder.tailnet.v2.TelemetryEvent.client_type:type_name -> coder.tailnet.v2.TelemetryEvent.ClientType - 29, // 24: coder.tailnet.v2.TelemetryEvent.p2p_endpoint:type_name -> coder.tailnet.v2.TelemetryEvent.P2PEndpoint - 30, // 25: coder.tailnet.v2.TelemetryEvent.log_ip_hashes:type_name -> coder.tailnet.v2.TelemetryEvent.LogIpHashesEntry - 4, // 26: coder.tailnet.v2.TelemetryEvent.derp_map:type_name -> coder.tailnet.v2.DERPMap - 10, // 27: coder.tailnet.v2.TelemetryEvent.latest_netcheck:type_name -> coder.tailnet.v2.Netcheck - 33, // 28: coder.tailnet.v2.TelemetryEvent.connection_age:type_name -> google.protobuf.Duration - 33, // 29: coder.tailnet.v2.TelemetryEvent.connection_setup:type_name -> google.protobuf.Duration - 33, // 30: coder.tailnet.v2.TelemetryEvent.p2p_setup:type_name -> google.protobuf.Duration - 33, // 31: coder.tailnet.v2.TelemetryEvent.derp_latency:type_name -> google.protobuf.Duration - 33, // 32: coder.tailnet.v2.TelemetryEvent.p2p_latency:type_name -> google.protobuf.Duration - 34, // 33: coder.tailnet.v2.TelemetryEvent.throughput_mbits:type_name -> google.protobuf.FloatValue - 11, // 34: coder.tailnet.v2.TelemetryRequest.events:type_name -> coder.tailnet.v2.TelemetryEvent - 17, // 35: coder.tailnet.v2.DERPMap.HomeParams.region_score:type_name -> coder.tailnet.v2.DERPMap.HomeParams.RegionScoreEntry - 18, // 36: coder.tailnet.v2.DERPMap.Region.nodes:type_name -> coder.tailnet.v2.DERPMap.Region.Node - 15, // 37: coder.tailnet.v2.DERPMap.RegionsEntry.value:type_name -> coder.tailnet.v2.DERPMap.Region - 6, // 38: coder.tailnet.v2.CoordinateRequest.UpdateSelf.node:type_name -> coder.tailnet.v2.Node - 6, // 39: coder.tailnet.v2.CoordinateResponse.PeerUpdate.node:type_name -> coder.tailnet.v2.Node - 0, // 40: coder.tailnet.v2.CoordinateResponse.PeerUpdate.kind:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind - 33, // 41: coder.tailnet.v2.Netcheck.RegionV4LatencyEntry.value:type_name -> google.protobuf.Duration - 33, // 42: coder.tailnet.v2.Netcheck.RegionV6LatencyEntry.value:type_name -> google.protobuf.Duration - 9, // 43: coder.tailnet.v2.Netcheck.NetcheckIP.fields:type_name -> coder.tailnet.v2.IPFields - 9, // 44: coder.tailnet.v2.TelemetryEvent.P2PEndpoint.fields:type_name -> coder.tailnet.v2.IPFields - 9, // 45: coder.tailnet.v2.TelemetryEvent.LogIpHashesEntry.value:type_name -> coder.tailnet.v2.IPFields - 12, // 46: coder.tailnet.v2.Tailnet.PostTelemetry:input_type -> coder.tailnet.v2.TelemetryRequest - 5, // 47: coder.tailnet.v2.Tailnet.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest - 7, // 48: coder.tailnet.v2.Tailnet.Coordinate:input_type -> coder.tailnet.v2.CoordinateRequest - 13, // 49: coder.tailnet.v2.Tailnet.PostTelemetry:output_type -> coder.tailnet.v2.TelemetryResponse - 4, // 50: coder.tailnet.v2.Tailnet.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap - 8, // 51: coder.tailnet.v2.Tailnet.Coordinate:output_type -> coder.tailnet.v2.CoordinateResponse - 49, // [49:52] is the sub-list for method output_type - 46, // [46:49] is the sub-list for method input_type - 46, // [46:46] is the sub-list for extension type_name - 46, // [46:46] is the sub-list for extension extendee - 0, // [0:46] is the sub-list for field type_name + 32, // 12: coder.tailnet.v2.Netcheck.OSHasIPv6:type_name -> google.protobuf.BoolValue + 32, // 13: coder.tailnet.v2.Netcheck.MappingVariesByDestIP:type_name -> google.protobuf.BoolValue + 32, // 14: coder.tailnet.v2.Netcheck.HairPinning:type_name -> google.protobuf.BoolValue + 32, // 15: coder.tailnet.v2.Netcheck.UPnP:type_name -> google.protobuf.BoolValue + 32, // 16: coder.tailnet.v2.Netcheck.PMP:type_name -> google.protobuf.BoolValue + 32, // 17: coder.tailnet.v2.Netcheck.PCP:type_name -> google.protobuf.BoolValue + 26, // 18: coder.tailnet.v2.Netcheck.RegionV4Latency:type_name -> coder.tailnet.v2.Netcheck.RegionV4LatencyEntry + 27, // 19: coder.tailnet.v2.Netcheck.RegionV6Latency:type_name -> coder.tailnet.v2.Netcheck.RegionV6LatencyEntry + 28, // 20: coder.tailnet.v2.Netcheck.GlobalV4:type_name -> coder.tailnet.v2.Netcheck.NetcheckIP + 28, // 21: coder.tailnet.v2.Netcheck.GlobalV6:type_name -> coder.tailnet.v2.Netcheck.NetcheckIP + 31, // 22: coder.tailnet.v2.TelemetryEvent.time:type_name -> google.protobuf.Timestamp + 2, // 23: coder.tailnet.v2.TelemetryEvent.status:type_name -> coder.tailnet.v2.TelemetryEvent.Status + 3, // 24: coder.tailnet.v2.TelemetryEvent.client_type:type_name -> coder.tailnet.v2.TelemetryEvent.ClientType + 29, // 25: coder.tailnet.v2.TelemetryEvent.p2p_endpoint:type_name -> coder.tailnet.v2.TelemetryEvent.P2PEndpoint + 30, // 26: coder.tailnet.v2.TelemetryEvent.log_ip_hashes:type_name -> coder.tailnet.v2.TelemetryEvent.LogIpHashesEntry + 4, // 27: coder.tailnet.v2.TelemetryEvent.derp_map:type_name -> coder.tailnet.v2.DERPMap + 10, // 28: coder.tailnet.v2.TelemetryEvent.latest_netcheck:type_name -> coder.tailnet.v2.Netcheck + 33, // 29: coder.tailnet.v2.TelemetryEvent.connection_age:type_name -> google.protobuf.Duration + 33, // 30: coder.tailnet.v2.TelemetryEvent.connection_setup:type_name -> google.protobuf.Duration + 33, // 31: coder.tailnet.v2.TelemetryEvent.p2p_setup:type_name -> google.protobuf.Duration + 33, // 32: coder.tailnet.v2.TelemetryEvent.derp_latency:type_name -> google.protobuf.Duration + 33, // 33: coder.tailnet.v2.TelemetryEvent.p2p_latency:type_name -> google.protobuf.Duration + 34, // 34: coder.tailnet.v2.TelemetryEvent.throughput_mbits:type_name -> google.protobuf.FloatValue + 11, // 35: coder.tailnet.v2.TelemetryRequest.events:type_name -> coder.tailnet.v2.TelemetryEvent + 17, // 36: coder.tailnet.v2.DERPMap.HomeParams.region_score:type_name -> coder.tailnet.v2.DERPMap.HomeParams.RegionScoreEntry + 18, // 37: coder.tailnet.v2.DERPMap.Region.nodes:type_name -> coder.tailnet.v2.DERPMap.Region.Node + 15, // 38: coder.tailnet.v2.DERPMap.RegionsEntry.value:type_name -> coder.tailnet.v2.DERPMap.Region + 6, // 39: coder.tailnet.v2.CoordinateRequest.UpdateSelf.node:type_name -> coder.tailnet.v2.Node + 6, // 40: coder.tailnet.v2.CoordinateResponse.PeerUpdate.node:type_name -> coder.tailnet.v2.Node + 0, // 41: coder.tailnet.v2.CoordinateResponse.PeerUpdate.kind:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind + 33, // 42: coder.tailnet.v2.Netcheck.RegionV4LatencyEntry.value:type_name -> google.protobuf.Duration + 33, // 43: coder.tailnet.v2.Netcheck.RegionV6LatencyEntry.value:type_name -> google.protobuf.Duration + 9, // 44: coder.tailnet.v2.Netcheck.NetcheckIP.fields:type_name -> coder.tailnet.v2.IPFields + 9, // 45: coder.tailnet.v2.TelemetryEvent.P2PEndpoint.fields:type_name -> coder.tailnet.v2.IPFields + 9, // 46: coder.tailnet.v2.TelemetryEvent.LogIpHashesEntry.value:type_name -> coder.tailnet.v2.IPFields + 12, // 47: coder.tailnet.v2.Tailnet.PostTelemetry:input_type -> coder.tailnet.v2.TelemetryRequest + 5, // 48: coder.tailnet.v2.Tailnet.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest + 7, // 49: coder.tailnet.v2.Tailnet.Coordinate:input_type -> coder.tailnet.v2.CoordinateRequest + 13, // 50: coder.tailnet.v2.Tailnet.PostTelemetry:output_type -> coder.tailnet.v2.TelemetryResponse + 4, // 51: coder.tailnet.v2.Tailnet.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap + 8, // 52: coder.tailnet.v2.Tailnet.Coordinate:output_type -> coder.tailnet.v2.CoordinateResponse + 50, // [50:53] is the sub-list for method output_type + 47, // [47:50] is the sub-list for method input_type + 47, // [47:47] is the sub-list for extension type_name + 47, // [47:47] is the sub-list for extension extendee + 0, // [0:47] is the sub-list for field type_name } func init() { file_tailnet_proto_tailnet_proto_init() } diff --git a/tailnet/proto/tailnet.proto b/tailnet/proto/tailnet.proto index 720ef85281edb..bfd87e7f92188 100644 --- a/tailnet/proto/tailnet.proto +++ b/tailnet/proto/tailnet.proto @@ -119,9 +119,9 @@ message Netcheck { bool IPv4 = 3; bool IPv6CanSend = 4; bool IPv4CanSend = 5; - bool OSHasIPv6 = 6; - bool ICMPv4 = 7; + bool ICMPv4 = 6; + google.protobuf.BoolValue OSHasIPv6 = 7; google.protobuf.BoolValue MappingVariesByDestIP = 8; google.protobuf.BoolValue HairPinning = 9; google.protobuf.BoolValue UPnP = 10; diff --git a/tailnet/telemetry.go b/tailnet/telemetry.go index 7a5e0a22c63c4..1a82991169fec 100644 --- a/tailnet/telemetry.go +++ b/tailnet/telemetry.go @@ -5,12 +5,17 @@ import ( "crypto/sha256" "encoding/hex" "io" + "maps" "net/netip" "regexp" "strings" "sync" + "time" "golang.org/x/xerrors" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" "tailscale.com/tailcfg" "cdr.dev/slog" @@ -101,7 +106,7 @@ type TelemetryStore struct { cleanDerpMap *tailcfg.DERPMap derpMapFilter *regexp.Regexp - netInfo *tailcfg.NetInfo + netCheck *proto.Netcheck } var _ slog.Sink = &TelemetryStore{} @@ -126,10 +131,31 @@ func newTelemetryStore() (*TelemetryStore, error) { // getStore returns a deep copy of all current telemetry state. // TODO: Should this return a populated event instead? -func (b *TelemetryStore) getStore() ([]string, map[string]*proto.IPFields, *proto.DERPMap, *proto.Netcheck) { +func (b *TelemetryStore) getStore() *proto.TelemetryEvent { b.mu.Lock() defer b.mu.Unlock() - return append([]string{}, b.logs...), b.hashedIPs, DERPMapToProto(b.cleanDerpMap), NetInfoToProto(b.netInfo) + + hashedIPs := make(map[string]*proto.IPFields, len(b.hashedIPs)) + maps.Copy(hashedIPs, b.hashedIPs) + + return &proto.TelemetryEvent{ + Time: timestamppb.Now(), + // Deep-copies + Logs: append([]string{}, b.logs...), + LogIpHashes: hashedIPs, + DerpMap: DERPMapToProto(b.cleanDerpMap), + LatestNetcheck: b.netCheck, + + // TODO: + Application: "", + NodeIdRemote: 0, + P2PEndpoint: &proto.TelemetryEvent_P2PEndpoint{}, + HomeDerp: "", + ConnectionAge: &durationpb.Duration{}, + ConnectionSetup: &durationpb.Duration{}, + // TODO: We only calculate this in one place, do we really want it? + P2PSetup: &durationpb.Duration{}, + } } // Given a DERPMap, anonymise all IPs and hostnames. @@ -146,11 +172,11 @@ func (b *TelemetryStore) updateDerpMap(cur *tailcfg.DERPMap) { escapedCertName := regexp.QuoteMeta(n.CertName) names = append(names, escapedName, escapedCertName) - ipv4, _ := b.processIPLocked(n.IPv4) + ipv4, _, _ := b.processIPLocked(n.IPv4) n.IPv4 = ipv4 - ipv6, _ := b.processIPLocked(n.IPv6) + ipv6, _, _ := b.processIPLocked(n.IPv6) n.IPv6 = ipv6 - stunIP, _ := b.processIPLocked(n.STUNTestIP) + stunIP, _, _ := b.processIPLocked(n.STUNTestIP) n.STUNTestIP = stunIP hn := b.hashAddr(n.HostName) n.HostName = hn @@ -164,10 +190,48 @@ func (b *TelemetryStore) updateDerpMap(cur *tailcfg.DERPMap) { b.cleanDerpMap = cleanMap } +// Store an anonymized proto.Netcheck given a tailscale NetInfo. func (b *TelemetryStore) setNetInfo(ni *tailcfg.NetInfo) { b.mu.Lock() defer b.mu.Unlock() - b.netInfo = ni.Clone() + + b.netCheck = &proto.Netcheck{ + UDP: ni.UDP, + IPv6: ni.IPv6, + IPv4: ni.IPv4, + IPv6CanSend: ni.IPv6CanSend, + IPv4CanSend: ni.IPv4CanSend, + ICMPv4: ni.ICMPv4, + OSHasIPv6: wrapperspb.Bool(ni.OSHasIPv6.EqualBool(true)), + MappingVariesByDestIP: wrapperspb.Bool(ni.MappingVariesByDestIP.EqualBool(true)), + HairPinning: wrapperspb.Bool(ni.HairPinning.EqualBool(true)), + UPnP: wrapperspb.Bool(ni.UPnP.EqualBool(true)), + PMP: wrapperspb.Bool(ni.PMP.EqualBool(true)), + PCP: wrapperspb.Bool(ni.PCP.EqualBool(true)), + PreferredDERP: int64(ni.PreferredDERP), + RegionV4Latency: make(map[int64]*durationpb.Duration), + RegionV6Latency: make(map[int64]*durationpb.Duration), + } + v4hash, v4fields, err := b.processIPLocked(ni.GlobalV4) + if err == nil { + b.netCheck.GlobalV4 = &proto.Netcheck_NetcheckIP{ + Hash: v4hash, + Fields: v4fields, + } + } + v6hash, v6fields, err := b.processIPLocked(ni.GlobalV6) + if err == nil { + b.netCheck.GlobalV6 = &proto.Netcheck_NetcheckIP{ + Hash: v6hash, + Fields: v6fields, + } + } + for rid, seconds := range ni.DERPLatencyV4 { + b.netCheck.RegionV4Latency[int64(rid)] = durationpb.New(time.Duration(seconds * float64(time.Second))) + } + for rid, seconds := range ni.DERPLatencyV6 { + b.netCheck.RegionV6Latency[int64(rid)] = durationpb.New(time.Duration(seconds * float64(time.Second))) + } } // Write implements io.Writer. @@ -186,7 +250,7 @@ func (b *TelemetryStore) Write(p []byte) (n int, err error) { } // Anonymize IP addresses for _, match := range ipv4And6Regex.FindAllString(logLineAfterLevel, -1) { - hash, err := b.processIPLocked(match) + hash, _, err := b.processIPLocked(match) if err == nil { logLine = strings.ReplaceAll(logLine, match, hash) } @@ -216,10 +280,10 @@ func (b *TelemetryStore) Sync() { // to the cache. It will also add it to hashedIPs. // // b.mu must be held. -func (b *TelemetryStore) processIPLocked(ip string) (string, error) { +func (b *TelemetryStore) processIPLocked(ip string) (string, *proto.IPFields, error) { addr, err := netip.ParseAddr(ip) if err != nil { - return "", xerrors.Errorf("failed to parse IP %q: %w", ip, err) + return "", nil, xerrors.Errorf("failed to parse IP %q: %w", ip, err) } version := int32(4) if addr.Is6() { @@ -239,11 +303,12 @@ func (b *TelemetryStore) processIPLocked(ip string) (string, error) { } hashStr := b.hashAddr(ip) - b.hashedIPs[hashStr] = &proto.IPFields{ + fields := &proto.IPFields{ Version: version, Class: class, } - return hashStr, nil + b.hashedIPs[hashStr] = fields + return hashStr, fields, nil } func (b *TelemetryStore) hashAddr(addr string) string { diff --git a/tailnet/telemetry_internal_test.go b/tailnet/telemetry_internal_test.go index 04cf43c318b26..b696ba1e2c27d 100644 --- a/tailnet/telemetry_internal_test.go +++ b/tailnet/telemetry_internal_test.go @@ -26,12 +26,12 @@ func TestTelemetryStore(t *testing.T) { logger.Debug(ctx, "line2 fe80") logger.Debug(ctx, "line3 xxxx::x") - logs, hashes, _, _ := sink.getStore() - require.Len(t, logs, 3) - require.Len(t, hashes, 0) - require.Contains(t, logs[0], "line1") - require.Contains(t, logs[1], "line2 fe80") - require.Contains(t, logs[2], "line3 xxxx::x") + event := sink.getStore() + require.Len(t, event.Logs, 3) + require.Len(t, event.LogIpHashes, 0) + require.Contains(t, event.Logs[0], "line1") + require.Contains(t, event.Logs[1], "line2 fe80") + require.Contains(t, event.Logs[2], "line3 xxxx::x") }) t.Run("OneOrMoreIPs", func(t *testing.T) { @@ -117,24 +117,24 @@ func TestTelemetryStore(t *testing.T) { logger.Debug(ctx, fmt.Sprintf("line2: %s/24", c.ip)) logger.Debug(ctx, fmt.Sprintf("line3: %s foo (%s)", ipWithPort, c.ip)) - logs, ips, _, _ := sink.getStore() - require.Len(t, logs, 3) - require.Len(t, ips, 1) - for _, log := range logs { + event := sink.getStore() + require.Len(t, event.Logs, 3) + require.Len(t, event.LogIpHashes, 1) + for _, log := range event.Logs { t.Log(log) } // This only runs once since we only processed a single IP. - for expectedHash, ipFields := range ips { + for expectedHash, ipFields := range event.LogIpHashes { hashedIPWithPort := expectedHash + ":8080" if c.expectedVersion == 6 { hashedIPWithPort = fmt.Sprintf("[%s]:8080", expectedHash) } - require.Contains(t, logs[0], "line1") - require.Contains(t, logs[0], "ip="+expectedHash) - require.Contains(t, logs[1], fmt.Sprintf("line2: %s/24", expectedHash)) - require.Contains(t, logs[2], fmt.Sprintf("line3: %s foo (%s)", hashedIPWithPort, expectedHash)) + require.Contains(t, event.Logs[0], "line1") + require.Contains(t, event.Logs[0], "ip="+expectedHash) + require.Contains(t, event.Logs[1], fmt.Sprintf("line2: %s/24", expectedHash)) + require.Contains(t, event.Logs[2], fmt.Sprintf("line3: %s foo (%s)", hashedIPWithPort, expectedHash)) require.Equal(t, c.expectedVersion, ipFields.Version) require.Equal(t, c.expectedClass, ipFields.Class) @@ -179,18 +179,18 @@ func TestTelemetryStore(t *testing.T) { logger.Debug(ctx, "line2 1.2.3.4 asdf") logger.Debug(ctx, "line3 2001:db8::1 foo") - logs, ips, dm, _ := telemetry.getStore() - require.Len(t, logs, 3) - require.Len(t, ips, 3) - require.Len(t, dm.Regions[999].Nodes, 1) - node := dm.Regions[999].Nodes[0] + event := telemetry.getStore() + require.Len(t, event.Logs, 3) + require.Len(t, event.LogIpHashes, 3) + require.Len(t, event.DerpMap.Regions[999].Nodes, 1) + node := event.DerpMap.Regions[999].Nodes[0] require.NotContains(t, node.HostName, "coolderp.com") require.NotContains(t, node.Ipv4, "1.2.3.4") require.NotContains(t, node.Ipv6, "2001:db8::1") require.NotContains(t, node.StunTestIp, "5.6.7.8") - require.Contains(t, logs[0], node.HostName) - require.Contains(t, ips, node.StunTestIp) - require.Contains(t, ips, node.Ipv6) - require.Contains(t, ips, node.Ipv4) + require.Contains(t, event.Logs[0], node.HostName) + require.Contains(t, event.LogIpHashes, node.StunTestIp) + require.Contains(t, event.LogIpHashes, node.Ipv6) + require.Contains(t, event.LogIpHashes, node.Ipv4) }) } From 6a3480cdc9ad00e2e9fef78e3f5e67ba6e9eb7d1 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 2 Jul 2024 11:34:40 +0000 Subject: [PATCH 13/19] remove logs --- coderd/telemetry/telemetry.go | 7 - codersdk/workspacesdk/workspacesdk.go | 4 +- tailnet/configmaps.go | 4 +- tailnet/configmaps_internal_test.go | 36 ++-- tailnet/conn.go | 30 ++- tailnet/node.go | 4 +- tailnet/node_internal_test.go | 30 +-- tailnet/proto/tailnet.pb.go | 293 ++++++++++++-------------- tailnet/proto/tailnet.proto | 22 +- tailnet/telemetry.go | 173 ++------------- tailnet/telemetry_internal_test.go | 160 ++++---------- 11 files changed, 252 insertions(+), 511 deletions(-) diff --git a/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index 18733b8fce74c..16bb936d33ff9 100644 --- a/coderd/telemetry/telemetry.go +++ b/coderd/telemetry/telemetry.go @@ -1278,11 +1278,6 @@ func NetworkEventFromProto(proto *tailnetproto.TelemetryEvent) (NetworkEvent, er return NetworkEvent{}, xerrors.Errorf("parse id %q: %w", proto.Id, err) } - logIPHashes := make(map[string]NetworkEventIPFields, len(proto.LogIpHashes)) - for k, v := range proto.LogIpHashes { - logIPHashes[k] = ipFieldsFromProto(v) - } - return NetworkEvent{ ID: id, Time: proto.Time.AsTime(), @@ -1293,9 +1288,7 @@ func NetworkEventFromProto(proto *tailnetproto.TelemetryEvent) (NetworkEvent, er NodeIDSelf: proto.NodeIdSelf, NodeIDRemote: proto.NodeIdRemote, P2PEndpoint: p2pEndpointFromProto(proto.P2PEndpoint), - LogIPHashes: logIPHashes, HomeDERP: proto.HomeDerp, - Logs: proto.Logs, DERPMap: derpMapFromProto(proto.DerpMap), LatestNetcheck: netcheckFromProto(proto.LatestNetcheck), diff --git a/codersdk/workspacesdk/workspacesdk.go b/codersdk/workspacesdk/workspacesdk.go index ca4d8add730cc..ca5b9e0efc9c0 100644 --- a/codersdk/workspacesdk/workspacesdk.go +++ b/codersdk/workspacesdk/workspacesdk.go @@ -181,8 +181,8 @@ type DialAgentOptions struct { // CaptureHook is a callback that captures Disco packets and packets sent // into the tailnet tunnel. CaptureHook capture.Callback - // Whether the client will send network telemetry events - // Enable instead of Disable so it's initialized to false + // Whether the client will send network telemetry events. + // Enable instead of Disable so it's initialized to false (in tests). EnableTelemetry bool } diff --git a/tailnet/configmaps.go b/tailnet/configmaps.go index 1df3a13264d8c..5d609b90c4bd8 100644 --- a/tailnet/configmaps.go +++ b/tailnet/configmaps.go @@ -66,14 +66,14 @@ type configMaps struct { peers map[uuid.UUID]*peerLifecycle addresses []netip.Prefix derpMap *tailcfg.DERPMap - logger multiLogger + logger slog.Logger blockEndpoints bool // for testing clock clock.Clock } -func newConfigMaps(logger multiLogger, engine engineConfigurable, nodeID tailcfg.NodeID, nodeKey key.NodePrivate, discoKey key.DiscoPublic) *configMaps { +func newConfigMaps(logger slog.Logger, engine engineConfigurable, nodeID tailcfg.NodeID, nodeKey key.NodePrivate, discoKey key.DiscoPublic) *configMaps { pubKey := nodeKey.Public() c := &configMaps{ phased: phased{Cond: *(sync.NewCond(&sync.Mutex{}))}, diff --git a/tailnet/configmaps_internal_test.go b/tailnet/configmaps_internal_test.go index dc522789f5765..83b15387a9a43 100644 --- a/tailnet/configmaps_internal_test.go +++ b/tailnet/configmaps_internal_test.go @@ -29,7 +29,7 @@ import ( func TestConfigMaps_setAddresses_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -87,7 +87,7 @@ func TestConfigMaps_setAddresses_different(t *testing.T) { func TestConfigMaps_setAddresses_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -118,7 +118,7 @@ func TestConfigMaps_setAddresses_same(t *testing.T) { func TestConfigMaps_updatePeers_new(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -188,7 +188,7 @@ func TestConfigMaps_updatePeers_new(t *testing.T) { func TestConfigMaps_updatePeers_new_waitForHandshake_neverConfigures(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -232,7 +232,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_neverConfigures(t *testing. func TestConfigMaps_updatePeers_new_waitForHandshake_outOfOrder(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -303,7 +303,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_outOfOrder(t *testing.T) { func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -374,7 +374,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) { func TestConfigMaps_updatePeers_new_waitForHandshake_timeout(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -432,7 +432,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_timeout(t *testing.T) { func TestConfigMaps_updatePeers_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -491,7 +491,7 @@ func TestConfigMaps_updatePeers_same(t *testing.T) { func TestConfigMaps_updatePeers_disconnect(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -559,7 +559,7 @@ func TestConfigMaps_updatePeers_disconnect(t *testing.T) { func TestConfigMaps_updatePeers_lost(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -644,7 +644,7 @@ func TestConfigMaps_updatePeers_lost(t *testing.T) { func TestConfigMaps_updatePeers_lost_and_found(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -729,7 +729,7 @@ func TestConfigMaps_updatePeers_lost_and_found(t *testing.T) { func TestConfigMaps_setAllPeersLost(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -815,7 +815,7 @@ func TestConfigMaps_setAllPeersLost(t *testing.T) { func TestConfigMaps_setBlockEndpoints_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -859,7 +859,7 @@ func TestConfigMaps_setBlockEndpoints_different(t *testing.T) { func TestConfigMaps_setBlockEndpoints_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -902,7 +902,7 @@ func TestConfigMaps_setBlockEndpoints_same(t *testing.T) { func TestConfigMaps_setDERPMap_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -943,7 +943,7 @@ func TestConfigMaps_setDERPMap_different(t *testing.T) { func TestConfigMaps_setDERPMap_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -1012,7 +1012,7 @@ func TestConfigMaps_setDERPMap_same(t *testing.T) { func TestConfigMaps_fillPeerDiagnostics(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) @@ -1120,7 +1120,7 @@ func TestConfigMaps_updatePeers_nonexist(t *testing.T) { t.Run(k.String(), func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) fEng := newFakeEngineConfigurable() nodePrivateKey := key.NewNode() nodeID := tailcfg.NodeID(5) diff --git a/tailnet/conn.go b/tailnet/conn.go index 4808db3d80db5..86717102199ec 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -135,17 +135,13 @@ func NewConn(options *Options) (conn *Conn, err error) { return nil, xerrors.New("At least one IP range must be provided") } - var ( - logger = newMultiLogger(options.Logger) - telemetryStore *TelemetryStore - ) + var telemetryStore *TelemetryStore if options.TelemetrySink != nil { var err error telemetryStore, err = newTelemetryStore() if err != nil { - return nil, xerrors.Errorf("create telemetry log store: %w", err) + return nil, xerrors.Errorf("create telemetry store: %w", err) } - logger = logger.appendLogger(slog.Make(telemetryStore).Leveled(slog.LevelDebug)) } nodePrivateKey := key.NewNode() @@ -162,7 +158,7 @@ func NewConn(options *Options) (conn *Conn, err error) { nodeID = tailcfg.NodeID(uid) } - wireguardMonitor, err := netmon.New(Logger(logger.Named("net.wgmonitor"))) + wireguardMonitor, err := netmon.New(Logger(options.Logger.Named("net.wgmonitor"))) if err != nil { return nil, xerrors.Errorf("create wireguard link monitor: %w", err) } @@ -173,10 +169,10 @@ func NewConn(options *Options) (conn *Conn, err error) { }() dialer := &tsdial.Dialer{ - Logf: Logger(logger.Named("net.tsdial")), + Logf: Logger(options.Logger.Named("net.tsdial")), } sys := new(tsd.System) - wireguardEngine, err := wgengine.NewUserspaceEngine(Logger(logger.Named("net.wgengine")), wgengine.Config{ + wireguardEngine, err := wgengine.NewUserspaceEngine(Logger(options.Logger.Named("net.wgengine")), wgengine.Config{ NetMon: wireguardMonitor, Dialer: dialer, ListenPort: options.ListenPort, @@ -211,13 +207,13 @@ func NewConn(options *Options) (conn *Conn, err error) { if v, ok := os.LookupEnv(EnvMagicsockDebugLogging); ok { vBool, err := strconv.ParseBool(v) if err != nil { - logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging disabled due to invalid value %s=%q, use true or false", EnvMagicsockDebugLogging, v)) + options.Logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging disabled due to invalid value %s=%q, use true or false", EnvMagicsockDebugLogging, v)) } else { magicConn.SetDebugLoggingEnabled(vBool) - logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging set by %s=%t", EnvMagicsockDebugLogging, vBool)) + options.Logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging set by %s=%t", EnvMagicsockDebugLogging, vBool)) } } else { - logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging disabled, use %s=true to enable", EnvMagicsockDebugLogging)) + options.Logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging disabled, use %s=true to enable", EnvMagicsockDebugLogging)) } // Update the keys for the magic connection! @@ -227,7 +223,7 @@ func NewConn(options *Options) (conn *Conn, err error) { } netStack, err := netstack.Create( - Logger(logger.Named("net.netstack")), + Logger(options.Logger.Named("net.netstack")), sys.Tun.Get(), wireguardEngine, magicConn, @@ -245,7 +241,7 @@ func NewConn(options *Options) (conn *Conn, err error) { wireguardEngine = wgengine.NewWatchdog(wireguardEngine) cfgMaps := newConfigMaps( - logger, + options.Logger, wireguardEngine, nodeID, nodePrivateKey, @@ -258,7 +254,7 @@ func NewConn(options *Options) (conn *Conn, err error) { cfgMaps.setBlockEndpoints(options.BlockEndpoints) nodeUp := newNodeUpdater( - logger, + options.Logger, nil, nodeID, nodePrivateKey.Public(), @@ -281,7 +277,7 @@ func NewConn(options *Options) (conn *Conn, err error) { id: uuid.New(), nodeID: nodeID, closed: make(chan struct{}), - logger: logger, + logger: options.Logger, magicConn: magicConn, dialer: dialer, listeners: map[listenKey]*listener{}, @@ -344,7 +340,7 @@ type Conn struct { nodeID tailcfg.NodeID mutex sync.Mutex closed chan struct{} - logger multiLogger + logger slog.Logger dialer *tsdial.Dialer tunDevice *tstun.Wrapper diff --git a/tailnet/node.go b/tailnet/node.go index 165bd85b0314c..858af3ad71e24 100644 --- a/tailnet/node.go +++ b/tailnet/node.go @@ -22,7 +22,7 @@ type nodeUpdater struct { closing bool // static - logger multiLogger + logger slog.Logger id tailcfg.NodeID key key.NodePublic discoKey key.DiscoPublic @@ -96,7 +96,7 @@ func (u *nodeUpdater) close() { } func newNodeUpdater( - logger multiLogger, callback func(n *Node), + logger slog.Logger, callback func(n *Node), id tailcfg.NodeID, np key.NodePublic, dp key.DiscoPublic, ) *nodeUpdater { u := &nodeUpdater{ diff --git a/tailnet/node_internal_test.go b/tailnet/node_internal_test.go index 6c471f9b67e28..577ce55a832cc 100644 --- a/tailnet/node_internal_test.go +++ b/tailnet/node_internal_test.go @@ -22,7 +22,7 @@ import ( func TestNodeUpdater_setNetInfo_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -75,7 +75,7 @@ func TestNodeUpdater_setNetInfo_different(t *testing.T) { func TestNodeUpdater_setNetInfo_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -116,7 +116,7 @@ func TestNodeUpdater_setNetInfo_same(t *testing.T) { func TestNodeUpdater_setDERPForcedWebsocket_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -155,7 +155,7 @@ func TestNodeUpdater_setDERPForcedWebsocket_different(t *testing.T) { func TestNodeUpdater_setDERPForcedWebsocket_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -193,7 +193,7 @@ func TestNodeUpdater_setDERPForcedWebsocket_same(t *testing.T) { func TestNodeUpdater_setStatus_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -243,7 +243,7 @@ func TestNodeUpdater_setStatus_different(t *testing.T) { func TestNodeUpdater_setStatus_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -283,7 +283,7 @@ func TestNodeUpdater_setStatus_same(t *testing.T) { func TestNodeUpdater_setStatus_error(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -321,7 +321,7 @@ func TestNodeUpdater_setStatus_error(t *testing.T) { func TestNodeUpdater_setStatus_outdated(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -363,7 +363,7 @@ func TestNodeUpdater_setStatus_outdated(t *testing.T) { func TestNodeUpdater_setAddresses_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -404,7 +404,7 @@ func TestNodeUpdater_setAddresses_different(t *testing.T) { func TestNodeUpdater_setAddresses_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -443,7 +443,7 @@ func TestNodeUpdater_setAddresses_same(t *testing.T) { func TestNodeUpdater_setCallback(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -484,7 +484,7 @@ func TestNodeUpdater_setCallback(t *testing.T) { func TestNodeUpdater_setBlockEndpoints_different(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -533,7 +533,7 @@ func TestNodeUpdater_setBlockEndpoints_different(t *testing.T) { func TestNodeUpdater_setBlockEndpoints_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -571,7 +571,7 @@ func TestNodeUpdater_setBlockEndpoints_same(t *testing.T) { func TestNodeUpdater_fillPeerDiagnostics(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() @@ -619,7 +619,7 @@ func TestNodeUpdater_fillPeerDiagnostics(t *testing.T) { func TestNodeUpdater_fillPeerDiagnostics_noCallback(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) - logger := newMultiLogger(slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) id := tailcfg.NodeID(1) nodeKey := key.NewNode().Public() discoKey := key.NewDisco().Public() diff --git a/tailnet/proto/tailnet.pb.go b/tailnet/proto/tailnet.pb.go index 1993b4bfee3cb..f777b956beffa 100644 --- a/tailnet/proto/tailnet.pb.go +++ b/tailnet/proto/tailnet.pb.go @@ -640,8 +640,8 @@ type Netcheck struct { IPv4 bool `protobuf:"varint,3,opt,name=IPv4,proto3" json:"IPv4,omitempty"` IPv6CanSend bool `protobuf:"varint,4,opt,name=IPv6CanSend,proto3" json:"IPv6CanSend,omitempty"` IPv4CanSend bool `protobuf:"varint,5,opt,name=IPv4CanSend,proto3" json:"IPv4CanSend,omitempty"` - ICMPv4 bool `protobuf:"varint,7,opt,name=ICMPv4,proto3" json:"ICMPv4,omitempty"` - OSHasIPv6 *wrapperspb.BoolValue `protobuf:"bytes,6,opt,name=OSHasIPv6,proto3" json:"OSHasIPv6,omitempty"` + ICMPv4 bool `protobuf:"varint,6,opt,name=ICMPv4,proto3" json:"ICMPv4,omitempty"` + OSHasIPv6 *wrapperspb.BoolValue `protobuf:"bytes,7,opt,name=OSHasIPv6,proto3" json:"OSHasIPv6,omitempty"` MappingVariesByDestIP *wrapperspb.BoolValue `protobuf:"bytes,8,opt,name=MappingVariesByDestIP,proto3" json:"MappingVariesByDestIP,omitempty"` HairPinning *wrapperspb.BoolValue `protobuf:"bytes,9,opt,name=HairPinning,proto3" json:"HairPinning,omitempty"` UPnP *wrapperspb.BoolValue `protobuf:"bytes,10,opt,name=UPnP,proto3" json:"UPnP,omitempty"` @@ -819,17 +819,15 @@ type TelemetryEvent struct { NodeIdSelf uint64 `protobuf:"varint,7,opt,name=node_id_self,json=nodeIdSelf,proto3" json:"node_id_self,omitempty"` NodeIdRemote uint64 `protobuf:"varint,8,opt,name=node_id_remote,json=nodeIdRemote,proto3" json:"node_id_remote,omitempty"` P2PEndpoint *TelemetryEvent_P2PEndpoint `protobuf:"bytes,9,opt,name=p2p_endpoint,json=p2pEndpoint,proto3" json:"p2p_endpoint,omitempty"` - LogIpHashes map[string]*IPFields `protobuf:"bytes,10,rep,name=log_ip_hashes,json=logIpHashes,proto3" json:"log_ip_hashes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - HomeDerp string `protobuf:"bytes,11,opt,name=home_derp,json=homeDerp,proto3" json:"home_derp,omitempty"` - Logs []string `protobuf:"bytes,12,rep,name=logs,proto3" json:"logs,omitempty"` - DerpMap *DERPMap `protobuf:"bytes,13,opt,name=derp_map,json=derpMap,proto3" json:"derp_map,omitempty"` - LatestNetcheck *Netcheck `protobuf:"bytes,14,opt,name=latest_netcheck,json=latestNetcheck,proto3" json:"latest_netcheck,omitempty"` - ConnectionAge *durationpb.Duration `protobuf:"bytes,15,opt,name=connection_age,json=connectionAge,proto3" json:"connection_age,omitempty"` - ConnectionSetup *durationpb.Duration `protobuf:"bytes,16,opt,name=connection_setup,json=connectionSetup,proto3" json:"connection_setup,omitempty"` - P2PSetup *durationpb.Duration `protobuf:"bytes,17,opt,name=p2p_setup,json=p2pSetup,proto3" json:"p2p_setup,omitempty"` - DerpLatency *durationpb.Duration `protobuf:"bytes,18,opt,name=derp_latency,json=derpLatency,proto3" json:"derp_latency,omitempty"` - P2PLatency *durationpb.Duration `protobuf:"bytes,19,opt,name=p2p_latency,json=p2pLatency,proto3" json:"p2p_latency,omitempty"` - ThroughputMbits *wrapperspb.FloatValue `protobuf:"bytes,20,opt,name=throughput_mbits,json=throughputMbits,proto3" json:"throughput_mbits,omitempty"` + HomeDerp string `protobuf:"bytes,10,opt,name=home_derp,json=homeDerp,proto3" json:"home_derp,omitempty"` + DerpMap *DERPMap `protobuf:"bytes,11,opt,name=derp_map,json=derpMap,proto3" json:"derp_map,omitempty"` + LatestNetcheck *Netcheck `protobuf:"bytes,12,opt,name=latest_netcheck,json=latestNetcheck,proto3" json:"latest_netcheck,omitempty"` + ConnectionAge *durationpb.Duration `protobuf:"bytes,13,opt,name=connection_age,json=connectionAge,proto3" json:"connection_age,omitempty"` + ConnectionSetup *durationpb.Duration `protobuf:"bytes,14,opt,name=connection_setup,json=connectionSetup,proto3" json:"connection_setup,omitempty"` + P2PSetup *durationpb.Duration `protobuf:"bytes,15,opt,name=p2p_setup,json=p2pSetup,proto3" json:"p2p_setup,omitempty"` + DerpLatency *durationpb.Duration `protobuf:"bytes,16,opt,name=derp_latency,json=derpLatency,proto3" json:"derp_latency,omitempty"` + P2PLatency *durationpb.Duration `protobuf:"bytes,17,opt,name=p2p_latency,json=p2pLatency,proto3" json:"p2p_latency,omitempty"` + ThroughputMbits *wrapperspb.FloatValue `protobuf:"bytes,18,opt,name=throughput_mbits,json=throughputMbits,proto3" json:"throughput_mbits,omitempty"` } func (x *TelemetryEvent) Reset() { @@ -927,13 +925,6 @@ func (x *TelemetryEvent) GetP2PEndpoint() *TelemetryEvent_P2PEndpoint { return nil } -func (x *TelemetryEvent) GetLogIpHashes() map[string]*IPFields { - if x != nil { - return x.LogIpHashes - } - return nil -} - func (x *TelemetryEvent) GetHomeDerp() string { if x != nil { return x.HomeDerp @@ -941,13 +932,6 @@ func (x *TelemetryEvent) GetHomeDerp() string { return "" } -func (x *TelemetryEvent) GetLogs() []string { - if x != nil { - return x.Logs - } - return nil -} - func (x *TelemetryEvent) GetDerpMap() *DERPMap { if x != nil { return x.DerpMap @@ -1930,9 +1914,9 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x49, 0x50, 0x76, 0x36, 0x43, 0x61, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x49, 0x50, 0x76, 0x34, 0x43, 0x61, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x49, 0x50, 0x76, 0x34, 0x43, 0x61, 0x6e, - 0x53, 0x65, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x49, 0x43, 0x4d, 0x50, 0x76, 0x34, 0x18, 0x07, + 0x53, 0x65, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x49, 0x43, 0x4d, 0x50, 0x76, 0x34, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x49, 0x43, 0x4d, 0x50, 0x76, 0x34, 0x12, 0x38, 0x0a, 0x09, - 0x4f, 0x53, 0x48, 0x61, 0x73, 0x49, 0x50, 0x76, 0x36, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x4f, 0x53, 0x48, 0x61, 0x73, 0x49, 0x50, 0x76, 0x36, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x09, 0x4f, 0x53, 0x48, 0x61, 0x73, 0x49, 0x50, 0x76, 0x36, 0x12, 0x50, 0x0a, 0x15, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, @@ -1992,7 +1976,7 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x68, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, - 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0xff, 0x0a, 0x0a, 0x0e, 0x54, 0x65, 0x6c, + 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0xb8, 0x09, 0x0a, 0x0e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, @@ -2021,93 +2005,81 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x32, 0x70, - 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x55, 0x0a, 0x0d, 0x6c, 0x6f, 0x67, 0x5f, - 0x69, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x31, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x2e, 0x4c, 0x6f, 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x0b, 0x6c, 0x6f, 0x67, 0x49, 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x12, - 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x6d, 0x65, 0x5f, 0x64, 0x65, 0x72, 0x70, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x6d, 0x65, 0x44, 0x65, 0x72, 0x70, 0x12, 0x12, 0x0a, 0x04, - 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, - 0x12, 0x34, 0x0a, 0x08, 0x64, 0x65, 0x72, 0x70, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x0d, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, - 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x52, 0x07, 0x64, - 0x65, 0x72, 0x70, 0x4d, 0x61, 0x70, 0x12, 0x43, 0x0a, 0x0f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, - 0x5f, 0x6e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0e, 0x6c, 0x61, 0x74, - 0x65, 0x73, 0x74, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x40, 0x0a, 0x0e, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x0f, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x67, 0x65, 0x12, 0x44, 0x0a, - 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x74, 0x75, - 0x70, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, - 0x74, 0x75, 0x70, 0x12, 0x36, 0x0a, 0x09, 0x70, 0x32, 0x70, 0x5f, 0x73, 0x65, 0x74, 0x75, 0x70, - 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x08, 0x70, 0x32, 0x70, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, 0x3c, 0x0a, 0x0c, 0x64, - 0x65, 0x72, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, - 0x72, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x3a, 0x0a, 0x0b, 0x70, 0x32, 0x70, - 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x70, 0x32, 0x70, 0x4c, 0x61, - 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x46, 0x0a, 0x10, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, - 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x62, 0x69, 0x74, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x74, 0x68, - 0x72, 0x6f, 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, 0x4d, 0x62, 0x69, 0x74, 0x73, 0x1a, 0x69, 0x0a, - 0x0b, 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, - 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, - 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, - 0x70, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, + 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x6f, 0x6d, 0x65, + 0x5f, 0x64, 0x65, 0x72, 0x70, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x6d, + 0x65, 0x44, 0x65, 0x72, 0x70, 0x12, 0x34, 0x0a, 0x08, 0x64, 0x65, 0x72, 0x70, 0x5f, 0x6d, 0x61, + 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, + 0x61, 0x70, 0x52, 0x07, 0x64, 0x65, 0x72, 0x70, 0x4d, 0x61, 0x70, 0x12, 0x43, 0x0a, 0x0f, 0x6c, + 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, - 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, - 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x5a, 0x0a, 0x10, 0x4c, 0x6f, 0x67, 0x49, - 0x70, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x49, 0x50, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x22, 0x29, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, - 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, - 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x01, 0x22, - 0x39, 0x0a, 0x0a, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, - 0x03, 0x43, 0x4c, 0x49, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, - 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x44, 0x45, 0x52, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, - 0x07, 0x57, 0x53, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x10, 0x03, 0x22, 0x4c, 0x0a, 0x10, 0x54, 0x65, - 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, - 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x54, 0x65, 0x6c, 0x65, - 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x98, 0x02, - 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x12, 0x58, 0x0a, 0x0d, 0x50, 0x6f, 0x73, - 0x74, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, - 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, - 0x50, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, - 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, - 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x43, - 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, - 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, + 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x52, 0x0e, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x4e, 0x65, 0x74, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x12, 0x40, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, + 0x67, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, + 0x67, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x73, 0x65, 0x74, 0x75, 0x70, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, 0x36, 0x0a, 0x09, 0x70, 0x32, 0x70, 0x5f, + 0x73, 0x65, 0x74, 0x75, 0x70, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x70, 0x32, 0x70, 0x53, 0x65, 0x74, 0x75, 0x70, + 0x12, 0x3c, 0x0a, 0x0c, 0x64, 0x65, 0x72, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, + 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x72, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x3a, + 0x0a, 0x0b, 0x70, 0x32, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x11, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, + 0x70, 0x32, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x46, 0x0a, 0x10, 0x74, 0x68, + 0x72, 0x6f, 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x62, 0x69, 0x74, 0x73, 0x18, 0x12, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x0f, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x70, 0x75, 0x74, 0x4d, 0x62, 0x69, + 0x74, 0x73, 0x1a, 0x69, 0x0a, 0x0b, 0x50, 0x32, 0x50, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x06, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x50, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0x29, 0x0a, + 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x4e, 0x45, + 0x43, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, + 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x01, 0x22, 0x39, 0x0a, 0x0a, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x4c, 0x49, 0x10, 0x00, 0x12, + 0x09, 0x0a, 0x05, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, + 0x44, 0x45, 0x52, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x53, 0x50, 0x52, 0x4f, 0x58, + 0x59, 0x10, 0x03, 0x22, 0x4c, 0x0a, 0x10, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, + 0x65, 0x74, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x22, 0x13, 0x0a, 0x11, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x98, 0x02, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6c, 0x6e, + 0x65, 0x74, 0x12, 0x58, 0x0a, 0x0d, 0x50, 0x6f, 0x73, 0x74, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, + 0x74, 0x72, 0x79, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, + 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, + 0x65, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0e, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x32, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, + 0x61, 0x70, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, + 0x74, 0x65, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, + 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, + 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, + 0x01, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, + 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2123,7 +2095,7 @@ func file_tailnet_proto_tailnet_proto_rawDescGZIP() []byte { } var file_tailnet_proto_tailnet_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_tailnet_proto_tailnet_proto_msgTypes = make([]protoimpl.MessageInfo, 27) +var file_tailnet_proto_tailnet_proto_msgTypes = make([]protoimpl.MessageInfo, 26) var file_tailnet_proto_tailnet_proto_goTypes = []interface{}{ (CoordinateResponse_PeerUpdate_Kind)(0), // 0: coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind (IPFields_IPClass)(0), // 1: coder.tailnet.v2.IPFields.IPClass @@ -2155,16 +2127,15 @@ var file_tailnet_proto_tailnet_proto_goTypes = []interface{}{ nil, // 27: coder.tailnet.v2.Netcheck.RegionV6LatencyEntry (*Netcheck_NetcheckIP)(nil), // 28: coder.tailnet.v2.Netcheck.NetcheckIP (*TelemetryEvent_P2PEndpoint)(nil), // 29: coder.tailnet.v2.TelemetryEvent.P2PEndpoint - nil, // 30: coder.tailnet.v2.TelemetryEvent.LogIpHashesEntry - (*timestamppb.Timestamp)(nil), // 31: google.protobuf.Timestamp - (*wrapperspb.BoolValue)(nil), // 32: google.protobuf.BoolValue - (*durationpb.Duration)(nil), // 33: google.protobuf.Duration - (*wrapperspb.FloatValue)(nil), // 34: google.protobuf.FloatValue + (*timestamppb.Timestamp)(nil), // 30: google.protobuf.Timestamp + (*wrapperspb.BoolValue)(nil), // 31: google.protobuf.BoolValue + (*durationpb.Duration)(nil), // 32: google.protobuf.Duration + (*wrapperspb.FloatValue)(nil), // 33: google.protobuf.FloatValue } var file_tailnet_proto_tailnet_proto_depIdxs = []int32{ 14, // 0: coder.tailnet.v2.DERPMap.home_params:type_name -> coder.tailnet.v2.DERPMap.HomeParams 16, // 1: coder.tailnet.v2.DERPMap.regions:type_name -> coder.tailnet.v2.DERPMap.RegionsEntry - 31, // 2: coder.tailnet.v2.Node.as_of:type_name -> google.protobuf.Timestamp + 30, // 2: coder.tailnet.v2.Node.as_of:type_name -> google.protobuf.Timestamp 19, // 3: coder.tailnet.v2.Node.derp_latency:type_name -> coder.tailnet.v2.Node.DerpLatencyEntry 20, // 4: coder.tailnet.v2.Node.derp_forced_websocket:type_name -> coder.tailnet.v2.Node.DerpForcedWebsocketEntry 21, // 5: coder.tailnet.v2.CoordinateRequest.update_self:type_name -> coder.tailnet.v2.CoordinateRequest.UpdateSelf @@ -2174,52 +2145,50 @@ var file_tailnet_proto_tailnet_proto_depIdxs = []int32{ 24, // 9: coder.tailnet.v2.CoordinateRequest.ready_for_handshake:type_name -> coder.tailnet.v2.CoordinateRequest.ReadyForHandshake 25, // 10: coder.tailnet.v2.CoordinateResponse.peer_updates:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate 1, // 11: coder.tailnet.v2.IPFields.class:type_name -> coder.tailnet.v2.IPFields.IPClass - 32, // 12: coder.tailnet.v2.Netcheck.OSHasIPv6:type_name -> google.protobuf.BoolValue - 32, // 13: coder.tailnet.v2.Netcheck.MappingVariesByDestIP:type_name -> google.protobuf.BoolValue - 32, // 14: coder.tailnet.v2.Netcheck.HairPinning:type_name -> google.protobuf.BoolValue - 32, // 15: coder.tailnet.v2.Netcheck.UPnP:type_name -> google.protobuf.BoolValue - 32, // 16: coder.tailnet.v2.Netcheck.PMP:type_name -> google.protobuf.BoolValue - 32, // 17: coder.tailnet.v2.Netcheck.PCP:type_name -> google.protobuf.BoolValue + 31, // 12: coder.tailnet.v2.Netcheck.OSHasIPv6:type_name -> google.protobuf.BoolValue + 31, // 13: coder.tailnet.v2.Netcheck.MappingVariesByDestIP:type_name -> google.protobuf.BoolValue + 31, // 14: coder.tailnet.v2.Netcheck.HairPinning:type_name -> google.protobuf.BoolValue + 31, // 15: coder.tailnet.v2.Netcheck.UPnP:type_name -> google.protobuf.BoolValue + 31, // 16: coder.tailnet.v2.Netcheck.PMP:type_name -> google.protobuf.BoolValue + 31, // 17: coder.tailnet.v2.Netcheck.PCP:type_name -> google.protobuf.BoolValue 26, // 18: coder.tailnet.v2.Netcheck.RegionV4Latency:type_name -> coder.tailnet.v2.Netcheck.RegionV4LatencyEntry 27, // 19: coder.tailnet.v2.Netcheck.RegionV6Latency:type_name -> coder.tailnet.v2.Netcheck.RegionV6LatencyEntry 28, // 20: coder.tailnet.v2.Netcheck.GlobalV4:type_name -> coder.tailnet.v2.Netcheck.NetcheckIP 28, // 21: coder.tailnet.v2.Netcheck.GlobalV6:type_name -> coder.tailnet.v2.Netcheck.NetcheckIP - 31, // 22: coder.tailnet.v2.TelemetryEvent.time:type_name -> google.protobuf.Timestamp + 30, // 22: coder.tailnet.v2.TelemetryEvent.time:type_name -> google.protobuf.Timestamp 2, // 23: coder.tailnet.v2.TelemetryEvent.status:type_name -> coder.tailnet.v2.TelemetryEvent.Status 3, // 24: coder.tailnet.v2.TelemetryEvent.client_type:type_name -> coder.tailnet.v2.TelemetryEvent.ClientType 29, // 25: coder.tailnet.v2.TelemetryEvent.p2p_endpoint:type_name -> coder.tailnet.v2.TelemetryEvent.P2PEndpoint - 30, // 26: coder.tailnet.v2.TelemetryEvent.log_ip_hashes:type_name -> coder.tailnet.v2.TelemetryEvent.LogIpHashesEntry - 4, // 27: coder.tailnet.v2.TelemetryEvent.derp_map:type_name -> coder.tailnet.v2.DERPMap - 10, // 28: coder.tailnet.v2.TelemetryEvent.latest_netcheck:type_name -> coder.tailnet.v2.Netcheck - 33, // 29: coder.tailnet.v2.TelemetryEvent.connection_age:type_name -> google.protobuf.Duration - 33, // 30: coder.tailnet.v2.TelemetryEvent.connection_setup:type_name -> google.protobuf.Duration - 33, // 31: coder.tailnet.v2.TelemetryEvent.p2p_setup:type_name -> google.protobuf.Duration - 33, // 32: coder.tailnet.v2.TelemetryEvent.derp_latency:type_name -> google.protobuf.Duration - 33, // 33: coder.tailnet.v2.TelemetryEvent.p2p_latency:type_name -> google.protobuf.Duration - 34, // 34: coder.tailnet.v2.TelemetryEvent.throughput_mbits:type_name -> google.protobuf.FloatValue - 11, // 35: coder.tailnet.v2.TelemetryRequest.events:type_name -> coder.tailnet.v2.TelemetryEvent - 17, // 36: coder.tailnet.v2.DERPMap.HomeParams.region_score:type_name -> coder.tailnet.v2.DERPMap.HomeParams.RegionScoreEntry - 18, // 37: coder.tailnet.v2.DERPMap.Region.nodes:type_name -> coder.tailnet.v2.DERPMap.Region.Node - 15, // 38: coder.tailnet.v2.DERPMap.RegionsEntry.value:type_name -> coder.tailnet.v2.DERPMap.Region - 6, // 39: coder.tailnet.v2.CoordinateRequest.UpdateSelf.node:type_name -> coder.tailnet.v2.Node - 6, // 40: coder.tailnet.v2.CoordinateResponse.PeerUpdate.node:type_name -> coder.tailnet.v2.Node - 0, // 41: coder.tailnet.v2.CoordinateResponse.PeerUpdate.kind:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind - 33, // 42: coder.tailnet.v2.Netcheck.RegionV4LatencyEntry.value:type_name -> google.protobuf.Duration - 33, // 43: coder.tailnet.v2.Netcheck.RegionV6LatencyEntry.value:type_name -> google.protobuf.Duration - 9, // 44: coder.tailnet.v2.Netcheck.NetcheckIP.fields:type_name -> coder.tailnet.v2.IPFields - 9, // 45: coder.tailnet.v2.TelemetryEvent.P2PEndpoint.fields:type_name -> coder.tailnet.v2.IPFields - 9, // 46: coder.tailnet.v2.TelemetryEvent.LogIpHashesEntry.value:type_name -> coder.tailnet.v2.IPFields - 12, // 47: coder.tailnet.v2.Tailnet.PostTelemetry:input_type -> coder.tailnet.v2.TelemetryRequest - 5, // 48: coder.tailnet.v2.Tailnet.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest - 7, // 49: coder.tailnet.v2.Tailnet.Coordinate:input_type -> coder.tailnet.v2.CoordinateRequest - 13, // 50: coder.tailnet.v2.Tailnet.PostTelemetry:output_type -> coder.tailnet.v2.TelemetryResponse - 4, // 51: coder.tailnet.v2.Tailnet.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap - 8, // 52: coder.tailnet.v2.Tailnet.Coordinate:output_type -> coder.tailnet.v2.CoordinateResponse - 50, // [50:53] is the sub-list for method output_type - 47, // [47:50] is the sub-list for method input_type - 47, // [47:47] is the sub-list for extension type_name - 47, // [47:47] is the sub-list for extension extendee - 0, // [0:47] is the sub-list for field type_name + 4, // 26: coder.tailnet.v2.TelemetryEvent.derp_map:type_name -> coder.tailnet.v2.DERPMap + 10, // 27: coder.tailnet.v2.TelemetryEvent.latest_netcheck:type_name -> coder.tailnet.v2.Netcheck + 32, // 28: coder.tailnet.v2.TelemetryEvent.connection_age:type_name -> google.protobuf.Duration + 32, // 29: coder.tailnet.v2.TelemetryEvent.connection_setup:type_name -> google.protobuf.Duration + 32, // 30: coder.tailnet.v2.TelemetryEvent.p2p_setup:type_name -> google.protobuf.Duration + 32, // 31: coder.tailnet.v2.TelemetryEvent.derp_latency:type_name -> google.protobuf.Duration + 32, // 32: coder.tailnet.v2.TelemetryEvent.p2p_latency:type_name -> google.protobuf.Duration + 33, // 33: coder.tailnet.v2.TelemetryEvent.throughput_mbits:type_name -> google.protobuf.FloatValue + 11, // 34: coder.tailnet.v2.TelemetryRequest.events:type_name -> coder.tailnet.v2.TelemetryEvent + 17, // 35: coder.tailnet.v2.DERPMap.HomeParams.region_score:type_name -> coder.tailnet.v2.DERPMap.HomeParams.RegionScoreEntry + 18, // 36: coder.tailnet.v2.DERPMap.Region.nodes:type_name -> coder.tailnet.v2.DERPMap.Region.Node + 15, // 37: coder.tailnet.v2.DERPMap.RegionsEntry.value:type_name -> coder.tailnet.v2.DERPMap.Region + 6, // 38: coder.tailnet.v2.CoordinateRequest.UpdateSelf.node:type_name -> coder.tailnet.v2.Node + 6, // 39: coder.tailnet.v2.CoordinateResponse.PeerUpdate.node:type_name -> coder.tailnet.v2.Node + 0, // 40: coder.tailnet.v2.CoordinateResponse.PeerUpdate.kind:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind + 32, // 41: coder.tailnet.v2.Netcheck.RegionV4LatencyEntry.value:type_name -> google.protobuf.Duration + 32, // 42: coder.tailnet.v2.Netcheck.RegionV6LatencyEntry.value:type_name -> google.protobuf.Duration + 9, // 43: coder.tailnet.v2.Netcheck.NetcheckIP.fields:type_name -> coder.tailnet.v2.IPFields + 9, // 44: coder.tailnet.v2.TelemetryEvent.P2PEndpoint.fields:type_name -> coder.tailnet.v2.IPFields + 12, // 45: coder.tailnet.v2.Tailnet.PostTelemetry:input_type -> coder.tailnet.v2.TelemetryRequest + 5, // 46: coder.tailnet.v2.Tailnet.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest + 7, // 47: coder.tailnet.v2.Tailnet.Coordinate:input_type -> coder.tailnet.v2.CoordinateRequest + 13, // 48: coder.tailnet.v2.Tailnet.PostTelemetry:output_type -> coder.tailnet.v2.TelemetryResponse + 4, // 49: coder.tailnet.v2.Tailnet.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap + 8, // 50: coder.tailnet.v2.Tailnet.Coordinate:output_type -> coder.tailnet.v2.CoordinateResponse + 48, // [48:51] is the sub-list for method output_type + 45, // [45:48] is the sub-list for method input_type + 45, // [45:45] is the sub-list for extension type_name + 45, // [45:45] is the sub-list for extension extendee + 0, // [0:45] is the sub-list for field type_name } func init() { file_tailnet_proto_tailnet_proto_init() } @@ -2475,7 +2444,7 @@ func file_tailnet_proto_tailnet_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_tailnet_proto_tailnet_proto_rawDesc, NumEnums: 4, - NumMessages: 27, + NumMessages: 26, NumExtensions: 0, NumServices: 1, }, diff --git a/tailnet/proto/tailnet.proto b/tailnet/proto/tailnet.proto index bfd87e7f92188..6d025b1eb1749 100644 --- a/tailnet/proto/tailnet.proto +++ b/tailnet/proto/tailnet.proto @@ -169,18 +169,16 @@ message TelemetryEvent { uint64 node_id_self = 7; uint64 node_id_remote = 8; P2PEndpoint p2p_endpoint = 9; - map log_ip_hashes = 10; - string home_derp = 11; - repeated string logs = 12; - DERPMap derp_map = 13; - Netcheck latest_netcheck = 14; - - google.protobuf.Duration connection_age = 15; - google.protobuf.Duration connection_setup = 16; - google.protobuf.Duration p2p_setup = 17; - google.protobuf.Duration derp_latency = 18; - google.protobuf.Duration p2p_latency = 19; - google.protobuf.FloatValue throughput_mbits = 20; + string home_derp = 10; + DERPMap derp_map = 11; + Netcheck latest_netcheck = 12; + + google.protobuf.Duration connection_age = 13; + google.protobuf.Duration connection_setup = 14; + google.protobuf.Duration p2p_setup = 15; + google.protobuf.Duration derp_latency = 16; + google.protobuf.Duration p2p_latency = 17; + google.protobuf.FloatValue throughput_mbits = 18; } message TelemetryRequest { diff --git a/tailnet/telemetry.go b/tailnet/telemetry.go index 1a82991169fec..786fc670508d2 100644 --- a/tailnet/telemetry.go +++ b/tailnet/telemetry.go @@ -1,14 +1,9 @@ package tailnet import ( - "context" "crypto/sha256" "encoding/hex" - "io" - "maps" "net/netip" - "regexp" - "strings" "sync" "time" @@ -18,115 +13,30 @@ import ( "google.golang.org/protobuf/types/known/wrapperspb" "tailscale.com/tailcfg" - "cdr.dev/slog" - "cdr.dev/slog/sloggers/sloghuman" "github.com/coder/coder/v2/cryptorand" "github.com/coder/coder/v2/tailnet/proto" ) -var ipv4And6Regex = regexp.MustCompile(`(((::ffff:)?(25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}|([a-f0-9:]+:+)+[a-f0-9]+)`) - -// Used to store a number of slog logger, and a logger sink for creating network telemetry events -type multiLogger struct { - loggers []slog.Logger -} - -func newMultiLogger(loggers ...slog.Logger) multiLogger { - return multiLogger{loggers: loggers} -} - -func (m multiLogger) appendLogger(logger slog.Logger) multiLogger { - return multiLogger{loggers: append(m.loggers, logger)} -} - -func (m multiLogger) Critical(ctx context.Context, msg string, fields ...any) { - for _, i := range m.loggers { - i.Critical(ctx, msg, fields...) - } -} - -func (m multiLogger) Debug(ctx context.Context, msg string, fields ...any) { - for _, i := range m.loggers { - i.Debug(ctx, msg, fields...) - } -} - -func (m multiLogger) Error(ctx context.Context, msg string, fields ...any) { - for _, i := range m.loggers { - i.Error(ctx, msg, fields...) - } -} - -func (m multiLogger) Fatal(ctx context.Context, msg string, fields ...any) { - for _, i := range m.loggers { - i.Fatal(ctx, msg, fields...) - } -} - -func (m multiLogger) Info(ctx context.Context, msg string, fields ...any) { - for _, i := range m.loggers { - i.Info(ctx, msg, fields...) - } -} - -func (m multiLogger) Warn(ctx context.Context, msg string, fields ...any) { - for _, i := range m.loggers { - i.Warn(ctx, msg, fields...) - } -} - -func (m multiLogger) Named(name string) multiLogger { - var loggers []slog.Logger - for _, i := range m.loggers { - loggers = append(loggers, i.Named(name)) - } - return multiLogger{loggers: loggers} -} - -func (m multiLogger) With(fields ...slog.Field) multiLogger { - var loggers []slog.Logger - for _, i := range m.loggers { - loggers = append(loggers, i.With(fields...)) - } - return multiLogger{loggers: loggers} -} - // Responsible for storing and anonymizing networking telemetry state. -// Implements slog.Sink and io.Writer to store logs from `tailscale`. type TelemetryStore struct { - // Always self-referential - sink slog.Sink - mu sync.Mutex - // TODO: Store only useful logs - logs []string + mu sync.Mutex hashSalt string // A cache to avoid hashing the same IP or hostname multiple times. hashCache map[string]string - hashedIPs map[string]*proto.IPFields cleanDerpMap *tailcfg.DERPMap - derpMapFilter *regexp.Regexp - netCheck *proto.Netcheck + cleanNetCheck *proto.Netcheck } -var _ slog.Sink = &TelemetryStore{} - -var _ io.Writer = &TelemetryStore{} - func newTelemetryStore() (*TelemetryStore, error) { hashSalt, err := cryptorand.String(16) if err != nil { return nil, err } - out := &TelemetryStore{ - logs: []string{}, - hashSalt: hashSalt, - hashCache: make(map[string]string), - hashedIPs: make(map[string]*proto.IPFields), - derpMapFilter: regexp.MustCompile(`^$`), - } - out.sink = sloghuman.Sink(out) - return out, nil + return &TelemetryStore{ + hashSalt: hashSalt, + hashCache: make(map[string]string), + }, nil } // getStore returns a deep copy of all current telemetry state. @@ -135,16 +45,10 @@ func (b *TelemetryStore) getStore() *proto.TelemetryEvent { b.mu.Lock() defer b.mu.Unlock() - hashedIPs := make(map[string]*proto.IPFields, len(b.hashedIPs)) - maps.Copy(hashedIPs, b.hashedIPs) - return &proto.TelemetryEvent{ - Time: timestamppb.Now(), - // Deep-copies - Logs: append([]string{}, b.logs...), - LogIpHashes: hashedIPs, + Time: timestamppb.Now(), DerpMap: DERPMapToProto(b.cleanDerpMap), - LatestNetcheck: b.netCheck, + LatestNetcheck: b.cleanNetCheck, // TODO: Application: "", @@ -164,14 +68,9 @@ func (b *TelemetryStore) getStore() *proto.TelemetryEvent { func (b *TelemetryStore) updateDerpMap(cur *tailcfg.DERPMap) { b.mu.Lock() defer b.mu.Unlock() - var names []string cleanMap := cur.Clone() for _, r := range cleanMap.Regions { for _, n := range r.Nodes { - escapedName := regexp.QuoteMeta(n.HostName) - escapedCertName := regexp.QuoteMeta(n.CertName) - names = append(names, escapedName, escapedCertName) - ipv4, _, _ := b.processIPLocked(n.IPv4) n.IPv4 = ipv4 ipv6, _, _ := b.processIPLocked(n.IPv6) @@ -184,9 +83,6 @@ func (b *TelemetryStore) updateDerpMap(cur *tailcfg.DERPMap) { n.CertName = cn } } - if len(names) != 0 { - b.derpMapFilter = regexp.MustCompile((strings.Join(names, "|"))) - } b.cleanDerpMap = cleanMap } @@ -195,7 +91,7 @@ func (b *TelemetryStore) setNetInfo(ni *tailcfg.NetInfo) { b.mu.Lock() defer b.mu.Unlock() - b.netCheck = &proto.Netcheck{ + b.cleanNetCheck = &proto.Netcheck{ UDP: ni.UDP, IPv6: ni.IPv6, IPv4: ni.IPv4, @@ -214,66 +110,24 @@ func (b *TelemetryStore) setNetInfo(ni *tailcfg.NetInfo) { } v4hash, v4fields, err := b.processIPLocked(ni.GlobalV4) if err == nil { - b.netCheck.GlobalV4 = &proto.Netcheck_NetcheckIP{ + b.cleanNetCheck.GlobalV4 = &proto.Netcheck_NetcheckIP{ Hash: v4hash, Fields: v4fields, } } v6hash, v6fields, err := b.processIPLocked(ni.GlobalV6) if err == nil { - b.netCheck.GlobalV6 = &proto.Netcheck_NetcheckIP{ + b.cleanNetCheck.GlobalV6 = &proto.Netcheck_NetcheckIP{ Hash: v6hash, Fields: v6fields, } } for rid, seconds := range ni.DERPLatencyV4 { - b.netCheck.RegionV4Latency[int64(rid)] = durationpb.New(time.Duration(seconds * float64(time.Second))) + b.cleanNetCheck.RegionV4Latency[int64(rid)] = durationpb.New(time.Duration(seconds * float64(time.Second))) } for rid, seconds := range ni.DERPLatencyV6 { - b.netCheck.RegionV6Latency[int64(rid)] = durationpb.New(time.Duration(seconds * float64(time.Second))) - } -} - -// Write implements io.Writer. -func (b *TelemetryStore) Write(p []byte) (n int, err error) { - b.mu.Lock() - defer b.mu.Unlock() - - // sloghuman writes a full log line in a single Write call with a trailing - // newline. - logLine := strings.TrimSuffix(string(p), "\n") - - logLineSplit := strings.SplitN(logLine, "]", 2) - logLineAfterLevel := logLine - if len(logLineAfterLevel) == 2 { - logLineAfterLevel = logLineSplit[1] - } - // Anonymize IP addresses - for _, match := range ipv4And6Regex.FindAllString(logLineAfterLevel, -1) { - hash, _, err := b.processIPLocked(match) - if err == nil { - logLine = strings.ReplaceAll(logLine, match, hash) - } + b.cleanNetCheck.RegionV6Latency[int64(rid)] = durationpb.New(time.Duration(seconds * float64(time.Second))) } - // Anonymize derp map host names - for _, match := range b.derpMapFilter.FindAllString(logLineAfterLevel, -1) { - hash := b.hashAddr(match) - logLine = strings.ReplaceAll(logLine, match, hash) - } - - b.logs = append(b.logs, logLine) - return len(p), nil -} - -// LogEntry implements slog.Sink. -func (b *TelemetryStore) LogEntry(ctx context.Context, e slog.SinkEntry) { - // This will call (*bufferLogSink).Write - b.sink.LogEntry(ctx, e) -} - -// Sync implements slog.Sink. -func (b *TelemetryStore) Sync() { - b.sink.Sync() } // processIPLocked will look up the IP in the cache, or hash and salt it and add @@ -307,7 +161,6 @@ func (b *TelemetryStore) processIPLocked(ip string) (string, *proto.IPFields, er Version: version, Class: class, } - b.hashedIPs[hashStr] = fields return hashStr, fields, nil } diff --git a/tailnet/telemetry_internal_test.go b/tailnet/telemetry_internal_test.go index b696ba1e2c27d..dd9e12308be77 100644 --- a/tailnet/telemetry_internal_test.go +++ b/tailnet/telemetry_internal_test.go @@ -1,101 +1,56 @@ package tailnet import ( - "context" - "fmt" "testing" "github.com/stretchr/testify/require" "tailscale.com/tailcfg" - "cdr.dev/slog" "github.com/coder/coder/v2/tailnet/proto" ) func TestTelemetryStore(t *testing.T) { t.Parallel() - t.Run("NoIP", func(t *testing.T) { - t.Parallel() - ctx := context.Background() - sink, err := newTelemetryStore() - require.NoError(t, err) - logger := slog.Make(sink).Leveled(slog.LevelDebug) - - logger.Debug(ctx, "line1") - logger.Debug(ctx, "line2 fe80") - logger.Debug(ctx, "line3 xxxx::x") - - event := sink.getStore() - require.Len(t, event.Logs, 3) - require.Len(t, event.LogIpHashes, 0) - require.Contains(t, event.Logs[0], "line1") - require.Contains(t, event.Logs[1], "line2 fe80") - require.Contains(t, event.Logs[2], "line3 xxxx::x") - }) - - t.Run("OneOrMoreIPs", func(t *testing.T) { + t.Run("CleanIPs", func(t *testing.T) { t.Parallel() cases := []struct { name string - ip string + ipv4 string + ipv6 string expectedVersion int32 expectedClass proto.IPFields_IPClass }{ { - name: "IPv4/Public", - ip: "142.250.71.78", - expectedVersion: 4, - expectedClass: proto.IPFields_PUBLIC, - }, - { - name: "IPv4/Private", - ip: "192.168.0.1", - expectedVersion: 4, - expectedClass: proto.IPFields_PRIVATE, - }, - { - name: "IPv4/LinkLocal", - ip: "169.254.1.1", - expectedVersion: 4, - expectedClass: proto.IPFields_LINK_LOCAL, + name: "Public", + ipv4: "142.250.71.78", + ipv6: "2404:6800:4006:812::200e", + expectedClass: proto.IPFields_PUBLIC, }, { - name: "IPv4/Loopback", - ip: "127.0.0.1", - expectedVersion: 4, - expectedClass: proto.IPFields_LOOPBACK, + name: "Private", + ipv4: "192.168.0.1", + ipv6: "fd12:3456:789a:1::1", + expectedClass: proto.IPFields_PRIVATE, }, { - name: "IPv6/Public", - ip: "2404:6800:4006:812::200e", - expectedVersion: 6, - expectedClass: proto.IPFields_PUBLIC, + name: "LinkLocal", + ipv4: "169.254.1.1", + ipv6: "fe80::1", + expectedClass: proto.IPFields_LINK_LOCAL, }, { - name: "IPv6/Private", - ip: "fd12:3456:789a:1::1", - expectedVersion: 6, - expectedClass: proto.IPFields_PRIVATE, + name: "Loopback", + ipv4: "127.0.0.1", + ipv6: "::1", + expectedClass: proto.IPFields_LOOPBACK, }, { - name: "IPv6/LinkLocal", - ip: "fe80::1", - expectedVersion: 6, - expectedClass: proto.IPFields_LINK_LOCAL, - }, - { - name: "IPv6/Loopback", - ip: "::1", - expectedVersion: 6, - expectedClass: proto.IPFields_LOOPBACK, - }, - { - name: "IPv6/IPv4Mapped", - ip: "::ffff:1.2.3.4", - expectedVersion: 6, - expectedClass: proto.IPFields_PUBLIC, + name: "IPv4Mapped", + ipv4: "1.2.3.4", + ipv6: "::ffff:1.2.3.4", + expectedClass: proto.IPFields_PUBLIC, }, } @@ -103,52 +58,39 @@ func TestTelemetryStore(t *testing.T) { c := c t.Run(c.name, func(t *testing.T) { t.Parallel() - ctx := context.Background() - sink, err := newTelemetryStore() + telemetry, err := newTelemetryStore() require.NoError(t, err) - logger := slog.Make(sink).Leveled(slog.LevelDebug) - - ipWithPort := c.ip + ":8080" - if c.expectedVersion == 6 { - ipWithPort = fmt.Sprintf("[%s]:8080", c.ip) - } - logger.Debug(ctx, "line1", slog.F("ip", c.ip)) - logger.Debug(ctx, fmt.Sprintf("line2: %s/24", c.ip)) - logger.Debug(ctx, fmt.Sprintf("line3: %s foo (%s)", ipWithPort, c.ip)) - - event := sink.getStore() - require.Len(t, event.Logs, 3) - require.Len(t, event.LogIpHashes, 1) - for _, log := range event.Logs { - t.Log(log) - } - - // This only runs once since we only processed a single IP. - for expectedHash, ipFields := range event.LogIpHashes { - hashedIPWithPort := expectedHash + ":8080" - if c.expectedVersion == 6 { - hashedIPWithPort = fmt.Sprintf("[%s]:8080", expectedHash) - } - - require.Contains(t, event.Logs[0], "line1") - require.Contains(t, event.Logs[0], "ip="+expectedHash) - require.Contains(t, event.Logs[1], fmt.Sprintf("line2: %s/24", expectedHash)) - require.Contains(t, event.Logs[2], fmt.Sprintf("line3: %s foo (%s)", hashedIPWithPort, expectedHash)) - - require.Equal(t, c.expectedVersion, ipFields.Version) - require.Equal(t, c.expectedClass, ipFields.Class) - } + telemetry.setNetInfo(&tailcfg.NetInfo{ + GlobalV4: c.ipv4, + GlobalV6: c.ipv6, + }) + + event := telemetry.getStore() + + require.Equal(t, &proto.Netcheck_NetcheckIP{ + Hash: telemetry.hashCache[c.ipv4], + Fields: &proto.IPFields{ + Version: 4, + Class: c.expectedClass, + }, + }, event.LatestNetcheck.GlobalV4) + + require.Equal(t, &proto.Netcheck_NetcheckIP{ + Hash: telemetry.hashCache[c.ipv6], + Fields: &proto.IPFields{ + Version: 6, + Class: c.expectedClass, + }, + }, event.LatestNetcheck.GlobalV6) }) } }) t.Run("DerpMapClean", func(t *testing.T) { t.Parallel() - ctx := context.Background() telemetry, err := newTelemetryStore() require.NoError(t, err) - logger := slog.Make(telemetry).Leveled(slog.LevelDebug) derpMap := &tailcfg.DERPMap{ Regions: make(map[int]*tailcfg.DERPRegion), @@ -175,22 +117,12 @@ func TestTelemetryStore(t *testing.T) { } telemetry.updateDerpMap(derpMap) - logger.Debug(ctx, "line1 coolderp.com qwerty") - logger.Debug(ctx, "line2 1.2.3.4 asdf") - logger.Debug(ctx, "line3 2001:db8::1 foo") - event := telemetry.getStore() - require.Len(t, event.Logs, 3) - require.Len(t, event.LogIpHashes, 3) require.Len(t, event.DerpMap.Regions[999].Nodes, 1) node := event.DerpMap.Regions[999].Nodes[0] require.NotContains(t, node.HostName, "coolderp.com") require.NotContains(t, node.Ipv4, "1.2.3.4") require.NotContains(t, node.Ipv6, "2001:db8::1") require.NotContains(t, node.StunTestIp, "5.6.7.8") - require.Contains(t, event.Logs[0], node.HostName) - require.Contains(t, event.LogIpHashes, node.StunTestIp) - require.Contains(t, event.LogIpHashes, node.Ipv6) - require.Contains(t, event.LogIpHashes, node.Ipv4) }) } From f33ab36b1f85bf600b7a257787c47b9410fdf054 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 2 Jul 2024 13:15:49 +0000 Subject: [PATCH 14/19] populate latency, node ids, home derp --- coderd/telemetry/telemetry.go | 26 ++++++------ tailnet/conn.go | 37 ++++++++++------- tailnet/telemetry.go | 64 +++++++++++++++++++----------- tailnet/telemetry_internal_test.go | 4 +- 4 files changed, 78 insertions(+), 53 deletions(-) diff --git a/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index 16bb936d33ff9..53055c686f72b 100644 --- a/coderd/telemetry/telemetry.go +++ b/coderd/telemetry/telemetry.go @@ -1231,20 +1231,18 @@ func netcheckFromProto(proto *tailnetproto.Netcheck) Netcheck { // NetworkEvent and all related structs come from tailnet.proto. type NetworkEvent struct { - ID uuid.UUID `json:"id"` - Time time.Time `json:"time"` - Application string `json:"application"` - Status string `json:"status"` // connected, disconnected - DisconnectionReason string `json:"disconnection_reason"` - ClientType string `json:"client_type"` // cli, agent, coderd, wsproxy - NodeIDSelf uint64 `json:"node_id_self"` - NodeIDRemote uint64 `json:"node_id_remote"` - P2PEndpoint NetworkEventP2PEndpoint `json:"p2p_endpoint"` - LogIPHashes map[string]NetworkEventIPFields `json:"log_ip_hashes"` - HomeDERP string `json:"home_derp"` - Logs []string `json:"logs"` - DERPMap DERPMap `json:"derp_map"` - LatestNetcheck Netcheck `json:"latest_netcheck"` + ID uuid.UUID `json:"id"` + Time time.Time `json:"time"` + Application string `json:"application"` + Status string `json:"status"` // connected, disconnected + DisconnectionReason string `json:"disconnection_reason"` + ClientType string `json:"client_type"` // cli, agent, coderd, wsproxy + NodeIDSelf uint64 `json:"node_id_self"` + NodeIDRemote uint64 `json:"node_id_remote"` + P2PEndpoint NetworkEventP2PEndpoint `json:"p2p_endpoint"` + HomeDERP string `json:"home_derp"` + DERPMap DERPMap `json:"derp_map"` + LatestNetcheck Netcheck `json:"latest_netcheck"` ConnectionAge *time.Duration `json:"connection_age"` ConnectionSetup *time.Duration `json:"connection_setup"` diff --git a/tailnet/conn.go b/tailnet/conn.go index 86717102199ec..9d561d93811c5 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -275,7 +275,6 @@ func NewConn(options *Options) (conn *Conn, err error) { server := &Conn{ id: uuid.New(), - nodeID: nodeID, closed: make(chan struct{}), logger: options.Logger, magicConn: magicConn, @@ -337,7 +336,6 @@ func IPFromUUID(uid uuid.UUID) netip.Addr { type Conn struct { // ID must be unique to this connection id uuid.UUID - nodeID tailcfg.NodeID mutex sync.Mutex closed chan struct{} logger slog.Logger @@ -446,7 +444,7 @@ func (c *Conn) Ping(ctx context.Context, ip netip.Addr) (time.Duration, bool, *i dur, p2p, pr, err := c.pingWithType(ctx, ip, tailcfg.PingDisco) if err == nil { // TODO(ethanndickson): Is this too often? - _ = c.sendPingTelemetry(dur, p2p) + c.sendPingTelemetry(pr) } return dur, p2p, pr, err } @@ -525,7 +523,7 @@ func (c *Conn) AwaitReachable(ctx context.Context, ip netip.Addr) bool { case <-completedCtx.Done(): // TODO(ethanndickson): For now, I'm interpreting 'connected' as when the // agent is reachable. - c.sendConnectedTelemetry() + c.sendConnectedTelemetry(ip) return true case <-t.C: // Pings can take a while, so we can run multiple @@ -715,12 +713,16 @@ func (c *Conn) MagicsockServeHTTPDebug(w http.ResponseWriter, r *http.Request) { c.magicConn.ServeHTTPDebug(w, r) } -func (c *Conn) sendConnectedTelemetry() { +func (c *Conn) sendConnectedTelemetry(ip netip.Addr) { if c.telemetrySink == nil { return } e := c.newTelemetryEvent() e.Status = proto.TelemetryEvent_CONNECTED + pip, ok := c.wireguardEngine.PeerForIP(ip) + if ok { + e.NodeIdRemote = uint64(pip.Node.ID) + } c.telemetryWg.Add(1) go func() { defer c.telemetryWg.Done() @@ -743,33 +745,40 @@ func (c *Conn) SendSpeedtestTelemetry(throughputMbits float64) { } // nolint: revive -func (c *Conn) sendPingTelemetry(latency time.Duration, p2p bool) error { +func (c *Conn) sendPingTelemetry(pr *ipnstate.PingResult) { if c.telemetrySink == nil { - return nil + return } e := c.newTelemetryEvent() - e.Status = proto.TelemetryEvent_CONNECTED - if p2p { - e.P2PLatency = durationpb.New(latency) + + latency := durationpb.New(time.Duration(pr.LatencySeconds * float64(time.Second))) + if pr.Endpoint != "" { + e.P2PLatency = latency + // TODO(ethanndickson): This could be nil if we can't parse the endpoint + // But tailscale only ever sets the endpoint using `netip.AddrPort.String()` + // So it's infallible. + e.P2PEndpoint = c.telemeteryStore.toEndpoint(pr.Endpoint) } else { - e.DerpLatency = durationpb.New(latency) + e.DerpLatency = latency } + e.Status = proto.TelemetryEvent_CONNECTED c.telemetryWg.Add(1) go func() { defer c.telemetryWg.Done() c.telemetrySink.SendTelemetryEvent(e) }() - return nil } // The returned telemetry event will not have it's status set. func (c *Conn) newTelemetryEvent() *proto.TelemetryEvent { // Infallible id, _ := c.id.MarshalBinary() - event := c.telemeteryStore.getStore() + event := c.telemeteryStore.newEvent() event.ClientType = c.clientType event.Id = id - event.NodeIdSelf = uint64(c.nodeID) + selfNode := c.Node() + event.NodeIdSelf = uint64(selfNode.ID) + event.HomeDerp = strconv.Itoa(selfNode.PreferredDERP) return event } diff --git a/tailnet/telemetry.go b/tailnet/telemetry.go index 786fc670508d2..08d16028c0498 100644 --- a/tailnet/telemetry.go +++ b/tailnet/telemetry.go @@ -39,9 +39,8 @@ func newTelemetryStore() (*TelemetryStore, error) { }, nil } -// getStore returns a deep copy of all current telemetry state. -// TODO: Should this return a populated event instead? -func (b *TelemetryStore) getStore() *proto.TelemetryEvent { +// newEvent returns the current telemetry state as an event +func (b *TelemetryStore) newEvent() *proto.TelemetryEvent { b.mu.Lock() defer b.mu.Unlock() @@ -50,15 +49,11 @@ func (b *TelemetryStore) getStore() *proto.TelemetryEvent { DerpMap: DERPMapToProto(b.cleanDerpMap), LatestNetcheck: b.cleanNetCheck, - // TODO: + // TODO(ethanndickson): Application: "", - NodeIdRemote: 0, - P2PEndpoint: &proto.TelemetryEvent_P2PEndpoint{}, - HomeDerp: "", ConnectionAge: &durationpb.Duration{}, ConnectionSetup: &durationpb.Duration{}, - // TODO: We only calculate this in one place, do we really want it? - P2PSetup: &durationpb.Duration{}, + P2PSetup: &durationpb.Duration{}, } } @@ -130,6 +125,24 @@ func (b *TelemetryStore) setNetInfo(ni *tailcfg.NetInfo) { } } +func (b *TelemetryStore) toEndpoint(ipport string) *proto.TelemetryEvent_P2PEndpoint { + b.mu.Lock() + defer b.mu.Unlock() + + addrport, err := netip.ParseAddrPort(ipport) + if err != nil { + return nil + } + addr := addrport.Addr() + fields := addrToFields(addr) + hashStr := b.hashAddr(addr.String()) + return &proto.TelemetryEvent_P2PEndpoint{ + Hash: hashStr, + Port: int32(addrport.Port()), + Fields: fields, + } +} + // processIPLocked will look up the IP in the cache, or hash and salt it and add // to the cache. It will also add it to hashedIPs. // @@ -139,6 +152,24 @@ func (b *TelemetryStore) processIPLocked(ip string) (string, *proto.IPFields, er if err != nil { return "", nil, xerrors.Errorf("failed to parse IP %q: %w", ip, err) } + + fields := addrToFields(addr) + hashStr := b.hashAddr(ip) + return hashStr, fields, nil +} + +func (b *TelemetryStore) hashAddr(addr string) string { + if hashStr, ok := b.hashCache[addr]; ok { + return hashStr + } + + hash := sha256.Sum256([]byte(b.hashSalt + addr)) + hashStr := hex.EncodeToString(hash[:]) + b.hashCache[addr] = hashStr + return hashStr +} + +func addrToFields(addr netip.Addr) *proto.IPFields { version := int32(4) if addr.Is6() { version = 6 @@ -156,21 +187,8 @@ func (b *TelemetryStore) processIPLocked(ip string) (string, *proto.IPFields, er class = proto.IPFields_PRIVATE } - hashStr := b.hashAddr(ip) - fields := &proto.IPFields{ + return &proto.IPFields{ Version: version, Class: class, } - return hashStr, fields, nil -} - -func (b *TelemetryStore) hashAddr(addr string) string { - if hashStr, ok := b.hashCache[addr]; ok { - return hashStr - } - - hash := sha256.Sum256([]byte(b.hashSalt + addr)) - hashStr := hex.EncodeToString(hash[:]) - b.hashCache[addr] = hashStr - return hashStr } diff --git a/tailnet/telemetry_internal_test.go b/tailnet/telemetry_internal_test.go index dd9e12308be77..e92a09da91145 100644 --- a/tailnet/telemetry_internal_test.go +++ b/tailnet/telemetry_internal_test.go @@ -66,7 +66,7 @@ func TestTelemetryStore(t *testing.T) { GlobalV6: c.ipv6, }) - event := telemetry.getStore() + event := telemetry.newEvent() require.Equal(t, &proto.Netcheck_NetcheckIP{ Hash: telemetry.hashCache[c.ipv4], @@ -117,7 +117,7 @@ func TestTelemetryStore(t *testing.T) { } telemetry.updateDerpMap(derpMap) - event := telemetry.getStore() + event := telemetry.newEvent() require.Len(t, event.DerpMap.Regions[999].Nodes, 1) node := event.DerpMap.Regions[999].Nodes[0] require.NotContains(t, node.HostName, "coolderp.com") From 39183692f5339e7117fcba4fffc64a9b1cb03f0b Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 2 Jul 2024 13:23:29 +0000 Subject: [PATCH 15/19] update flake --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 087b65f630185..8f4b41356d434 100644 --- a/flake.nix +++ b/flake.nix @@ -97,7 +97,7 @@ name = "coder-${osArch}"; # Updated with ./scripts/update-flake.sh`. # This should be updated whenever go.mod changes! - vendorHash = "sha256-IbebvYg1E50elvojWbJICJiTzy5Y5bP7n57sGMENDFs="; + vendorHash = "sha256-5R2FelgM9NRYlse309NukEVh25pvusO2FXZx1VuWGoo="; proxyVendor = true; src = ./.; nativeBuildInputs = with pkgs; [ getopt openssl zstd ]; From b9cfa5289e4cee2e4ecd3bfe98a87c9101749046 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 2 Jul 2024 13:29:40 +0000 Subject: [PATCH 16/19] fixup --- codersdk/workspacesdk/connector_internal_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/codersdk/workspacesdk/connector_internal_test.go b/codersdk/workspacesdk/connector_internal_test.go index e128503b2ab29..0b7793e7a2cef 100644 --- a/codersdk/workspacesdk/connector_internal_test.go +++ b/codersdk/workspacesdk/connector_internal_test.go @@ -200,8 +200,6 @@ func TestTailnetAPIConnector_TelemetrySuccess(t *testing.T) { require.Equal(t, []byte("test event"), testEvents[0].Id) } -// Server doesn't support telemetry / server unimplemented telemetry - func TestTailnetAPIConnector_TelemetryUnimplemented(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) From c0684bbf60f2d7acdffb84b4db09675ebdb9e95f Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 2 Jul 2024 13:40:06 +0000 Subject: [PATCH 17/19] application field --- codersdk/workspacesdk/agentconn.go | 2 ++ codersdk/workspacesdk/workspacesdk.go | 2 ++ tailnet/conn.go | 6 ++---- tailnet/telemetry.go | 7 ++++++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/codersdk/workspacesdk/agentconn.go b/codersdk/workspacesdk/agentconn.go index 6700f5d935273..ed9da4c2a04bf 100644 --- a/codersdk/workspacesdk/agentconn.go +++ b/codersdk/workspacesdk/agentconn.go @@ -149,6 +149,7 @@ func (c *AgentConn) SSH(ctx context.Context) (*gonet.TCPConn, error) { return nil, xerrors.Errorf("workspace agent not reachable in time: %v", ctx.Err()) } + c.Conn.SendConnectedTelemetry(c.agentAddress(), tailnet.TelemetryApplicationSSH) return c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), AgentSSHPort)) } @@ -185,6 +186,7 @@ func (c *AgentConn) Speedtest(ctx context.Context, direction speedtest.Direction return nil, xerrors.Errorf("workspace agent not reachable in time: %v", ctx.Err()) } + c.Conn.SendConnectedTelemetry(c.agentAddress(), tailnet.TelemetryApplicationSpeedtest) speedConn, err := c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), AgentSpeedtestPort)) if err != nil { return nil, xerrors.Errorf("dial speedtest: %w", err) diff --git a/codersdk/workspacesdk/workspacesdk.go b/codersdk/workspacesdk/workspacesdk.go index ca5b9e0efc9c0..a38ed1c05c91d 100644 --- a/codersdk/workspacesdk/workspacesdk.go +++ b/codersdk/workspacesdk/workspacesdk.go @@ -21,6 +21,7 @@ import ( "cdr.dev/slog" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/tailnet" + "github.com/coder/coder/v2/tailnet/proto" ) // AgentIP is a static IPv6 address with the Tailscale prefix that is used to route @@ -256,6 +257,7 @@ func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options * Logger: options.Logger, BlockEndpoints: c.client.DisableDirectConnections || options.BlockEndpoints, CaptureHook: options.CaptureHook, + ClientType: proto.TelemetryEvent_CLI, TelemetrySink: telemetrySink, }) if err != nil { diff --git a/tailnet/conn.go b/tailnet/conn.go index 9d561d93811c5..4d7e905fcd59d 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -521,9 +521,6 @@ func (c *Conn) AwaitReachable(ctx context.Context, ip netip.Addr) bool { for { select { case <-completedCtx.Done(): - // TODO(ethanndickson): For now, I'm interpreting 'connected' as when the - // agent is reachable. - c.sendConnectedTelemetry(ip) return true case <-t.C: // Pings can take a while, so we can run multiple @@ -713,12 +710,13 @@ func (c *Conn) MagicsockServeHTTPDebug(w http.ResponseWriter, r *http.Request) { c.magicConn.ServeHTTPDebug(w, r) } -func (c *Conn) sendConnectedTelemetry(ip netip.Addr) { +func (c *Conn) SendConnectedTelemetry(ip netip.Addr, application string) { if c.telemetrySink == nil { return } e := c.newTelemetryEvent() e.Status = proto.TelemetryEvent_CONNECTED + e.Application = application pip, ok := c.wireguardEngine.PeerForIP(ip) if ok { e.NodeIdRemote = uint64(pip.Node.ID) diff --git a/tailnet/telemetry.go b/tailnet/telemetry.go index 08d16028c0498..2efd1c34b7c7c 100644 --- a/tailnet/telemetry.go +++ b/tailnet/telemetry.go @@ -17,6 +17,12 @@ import ( "github.com/coder/coder/v2/tailnet/proto" ) +// TODO(ethanndickson): How useful is the 'application' field? +const ( + TelemetryApplicationSSH string = "ssh" + TelemetryApplicationSpeedtest string = "speedtest" +) + // Responsible for storing and anonymizing networking telemetry state. type TelemetryStore struct { mu sync.Mutex @@ -50,7 +56,6 @@ func (b *TelemetryStore) newEvent() *proto.TelemetryEvent { LatestNetcheck: b.cleanNetCheck, // TODO(ethanndickson): - Application: "", ConnectionAge: &durationpb.Duration{}, ConnectionSetup: &durationpb.Duration{}, P2PSetup: &durationpb.Duration{}, From 67898fdb2de4ddb464d62bab5572f5ff28fc29c9 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 3 Jul 2024 03:57:14 +0000 Subject: [PATCH 18/19] review --- cli/ping.go | 2 +- cli/portforward.go | 2 +- cli/root.go | 10 +++--- cli/speedtest.go | 2 +- cli/ssh.go | 2 +- cli/testdata/coder_--help.golden | 6 +++- .../workspacesdk/connector_internal_test.go | 11 +++++- docs/cli.md | 5 ++- enterprise/cli/testdata/coder_--help.golden | 6 +++- tailnet/conn.go | 8 ++--- tailnet/telemetry.go | 11 +++--- tailnet/telemetry_internal_test.go | 35 +++++++++++++++---- 12 files changed, 69 insertions(+), 31 deletions(-) diff --git a/cli/ping.go b/cli/ping.go index 4d4e9308909dc..644754283ee58 100644 --- a/cli/ping.go +++ b/cli/ping.go @@ -58,7 +58,7 @@ func (r *RootCmd) ping() *serpent.Command { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") opts.BlockEndpoints = true } - if !r.noNetworkTelemetry { + if !r.disableNetworkTelemetry { opts.EnableTelemetry = true } conn, err := workspacesdk.New(client).DialAgent(ctx, workspaceAgent.ID, opts) diff --git a/cli/portforward.go b/cli/portforward.go index 2a0126032e3bb..bab85464a9a01 100644 --- a/cli/portforward.go +++ b/cli/portforward.go @@ -106,7 +106,7 @@ func (r *RootCmd) portForward() *serpent.Command { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") opts.BlockEndpoints = true } - if !r.noNetworkTelemetry { + if !r.disableNetworkTelemetry { opts.EnableTelemetry = true } conn, err := workspacesdk.New(client).DialAgent(ctx, workspaceAgent.ID, opts) diff --git a/cli/root.go b/cli/root.go index 82702162ae094..418915490b910 100644 --- a/cli/root.go +++ b/cli/root.go @@ -439,8 +439,8 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err { Flag: varDisableNetworkTelemetry, Env: "CODER_DISABLE_NETWORK_TELEMETRY", - Description: "Disable network telemetry.", - Value: serpent.BoolOf(&r.noNetworkTelemetry), + Description: "Disable network telemetry. Network telemetry is collected when connecting to workspaces using the CLI, and is forwarded to the server. If telemetry is also enabled on the server, it may be sent to Coder. Network telemetry is used to measure network quality and detect regressions.", + Value: serpent.BoolOf(&r.disableNetworkTelemetry), Group: globalGroup, }, { @@ -489,9 +489,9 @@ type RootCmd struct { disableDirect bool debugHTTP bool - noNetworkTelemetry bool - noVersionCheck bool - noFeatureWarning bool + disableNetworkTelemetry bool + noVersionCheck bool + noFeatureWarning bool } // InitClient authenticates the client with files from disk diff --git a/cli/speedtest.go b/cli/speedtest.go index 72cb3f714c15c..c31fc8e65defc 100644 --- a/cli/speedtest.go +++ b/cli/speedtest.go @@ -102,7 +102,7 @@ func (r *RootCmd) speedtest() *serpent.Command { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") opts.BlockEndpoints = true } - if !r.noNetworkTelemetry { + if !r.disableNetworkTelemetry { opts.EnableTelemetry = true } if pcapFile != "" { diff --git a/cli/ssh.go b/cli/ssh.go index 050c7d7fe48c3..8c5dc7bdc46a2 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -245,7 +245,7 @@ func (r *RootCmd) ssh() *serpent.Command { DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ Logger: logger, BlockEndpoints: r.disableDirect, - EnableTelemetry: !r.noNetworkTelemetry, + EnableTelemetry: !r.disableNetworkTelemetry, }) if err != nil { return xerrors.Errorf("dial agent: %w", err) diff --git a/cli/testdata/coder_--help.golden b/cli/testdata/coder_--help.golden index 294010e7678e6..ce220e95b1188 100644 --- a/cli/testdata/coder_--help.golden +++ b/cli/testdata/coder_--help.golden @@ -67,7 +67,11 @@ variables or flags. Disable direct (P2P) connections to workspaces. --disable-network-telemetry bool, $CODER_DISABLE_NETWORK_TELEMETRY - Disable network telemetry. + Disable network telemetry. Network telemetry is collected when + connecting to workspaces using the CLI, and is forwarded to the + server. If telemetry is also enabled on the server, it may be sent to + Coder. Network telemetry is used to measure network quality and detect + regressions. --global-config string, $CODER_CONFIG_DIR (default: ~/.config/coderv2) Path to the global `coder` config directory. diff --git a/codersdk/workspacesdk/connector_internal_test.go b/codersdk/workspacesdk/connector_internal_test.go index 0b7793e7a2cef..00463d2076016 100644 --- a/codersdk/workspacesdk/connector_internal_test.go +++ b/codersdk/workspacesdk/connector_internal_test.go @@ -231,10 +231,13 @@ func TestTailnetAPIConnector_TelemetryUnimplemented(t *testing.T) { fakeDRPCClient.telemeteryErorr = drpcerr.WithCode(xerrors.New("Unimplemented"), 0) uut.SendTelemetryEvent(&proto.TelemetryEvent{}) require.False(t, uut.telemetryUnavailable.Load()) + require.Equal(t, int64(1), atomic.LoadInt64(&fakeDRPCClient.postTelemetryCalls)) fakeDRPCClient.telemeteryErorr = drpcerr.WithCode(xerrors.New("Unimplemented"), drpcerr.Unimplemented) uut.SendTelemetryEvent(&proto.TelemetryEvent{}) require.True(t, uut.telemetryUnavailable.Load()) + uut.SendTelemetryEvent(&proto.TelemetryEvent{}) + require.Equal(t, int64(2), atomic.LoadInt64(&fakeDRPCClient.postTelemetryCalls)) } func TestTailnetAPIConnector_TelemetryNotRecognised(t *testing.T) { @@ -268,10 +271,13 @@ func TestTailnetAPIConnector_TelemetryNotRecognised(t *testing.T) { fakeDRPCClient.telemeteryErorr = drpc.ProtocolError.New("Protocol Error") uut.SendTelemetryEvent(&proto.TelemetryEvent{}) require.False(t, uut.telemetryUnavailable.Load()) + require.Equal(t, int64(1), atomic.LoadInt64(&fakeDRPCClient.postTelemetryCalls)) fakeDRPCClient.telemeteryErorr = drpc.ProtocolError.New("unknown rpc: /coder.tailnet.v2.Tailnet/PostTelemetry") uut.SendTelemetryEvent(&proto.TelemetryEvent{}) require.True(t, uut.telemetryUnavailable.Load()) + uut.SendTelemetryEvent(&proto.TelemetryEvent{}) + require.Equal(t, int64(2), atomic.LoadInt64(&fakeDRPCClient.postTelemetryCalls)) } type fakeTailnetConn struct{} @@ -294,7 +300,8 @@ func newFakeTailnetConn() *fakeTailnetConn { } type fakeDRPCClient struct { - telemeteryErorr error + postTelemetryCalls int64 + telemeteryErorr error fakeDRPPCMapStream } @@ -302,6 +309,7 @@ var _ proto.DRPCTailnetClient = &fakeDRPCClient{} func newFakeDRPCClient() *fakeDRPCClient { return &fakeDRPCClient{ + postTelemetryCalls: 0, fakeDRPPCMapStream: fakeDRPPCMapStream{ fakeDRPCStream: fakeDRPCStream{ ch: make(chan struct{}), @@ -322,6 +330,7 @@ func (*fakeDRPCClient) DRPCConn() drpc.Conn { // PostTelemetry implements proto.DRPCTailnetClient. func (f *fakeDRPCClient) PostTelemetry(_ context.Context, _ *proto.TelemetryRequest) (*proto.TelemetryResponse, error) { + atomic.AddInt64(&f.postTelemetryCalls, 1) return nil, f.telemeteryErorr } diff --git a/docs/cli.md b/docs/cli.md index 2962425499040..7f5364048e3ed 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -156,7 +156,10 @@ Disable direct (P2P) connections to workspaces. | Type | bool | | Environment | $CODER_DISABLE_NETWORK_TELEMETRY | -Disable network telemetry. +Disable network telemetry. Network telemetry is collected when connecting to +workspaces using the CLI, and is forwarded to the server. If telemetry is also +enabled on the server, it may be sent to Coder. Network telemetry is used to +measure network quality and detect regressions. ### --global-config diff --git a/enterprise/cli/testdata/coder_--help.golden b/enterprise/cli/testdata/coder_--help.golden index 6895cdde3c9f4..e575451922a5b 100644 --- a/enterprise/cli/testdata/coder_--help.golden +++ b/enterprise/cli/testdata/coder_--help.golden @@ -31,7 +31,11 @@ variables or flags. Disable direct (P2P) connections to workspaces. --disable-network-telemetry bool, $CODER_DISABLE_NETWORK_TELEMETRY - Disable network telemetry. + Disable network telemetry. Network telemetry is collected when + connecting to workspaces using the CLI, and is forwarded to the + server. If telemetry is also enabled on the server, it may be sent to + Coder. Network telemetry is used to measure network quality and detect + regressions. --global-config string, $CODER_CONFIG_DIR (default: ~/.config/coderv2) Path to the global `coder` config directory. diff --git a/tailnet/conn.go b/tailnet/conn.go index 4d7e905fcd59d..de6269dda2d08 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -334,7 +334,7 @@ func IPFromUUID(uid uuid.UUID) netip.Addr { // Conn is an actively listening Wireguard connection. type Conn struct { - // ID must be unique to this connection + // Unique ID used for telemetry. id uuid.UUID mutex sync.Mutex closed chan struct{} @@ -443,7 +443,6 @@ func (c *Conn) Status() *ipnstate.Status { func (c *Conn) Ping(ctx context.Context, ip netip.Addr) (time.Duration, bool, *ipnstate.PingResult, error) { dur, p2p, pr, err := c.pingWithType(ctx, ip, tailcfg.PingDisco) if err == nil { - // TODO(ethanndickson): Is this too often? c.sendPingTelemetry(pr) } return dur, p2p, pr, err @@ -742,7 +741,7 @@ func (c *Conn) SendSpeedtestTelemetry(throughputMbits float64) { }() } -// nolint: revive +// nolint:revive func (c *Conn) sendPingTelemetry(pr *ipnstate.PingResult) { if c.telemetrySink == nil { return @@ -752,9 +751,6 @@ func (c *Conn) sendPingTelemetry(pr *ipnstate.PingResult) { latency := durationpb.New(time.Duration(pr.LatencySeconds * float64(time.Second))) if pr.Endpoint != "" { e.P2PLatency = latency - // TODO(ethanndickson): This could be nil if we can't parse the endpoint - // But tailscale only ever sets the endpoint using `netip.AddrPort.String()` - // So it's infallible. e.P2PEndpoint = c.telemeteryStore.toEndpoint(pr.Endpoint) } else { e.DerpLatency = latency diff --git a/tailnet/telemetry.go b/tailnet/telemetry.go index 2efd1c34b7c7c..b8012e33a1ad4 100644 --- a/tailnet/telemetry.go +++ b/tailnet/telemetry.go @@ -17,7 +17,6 @@ import ( "github.com/coder/coder/v2/tailnet/proto" ) -// TODO(ethanndickson): How useful is the 'application' field? const ( TelemetryApplicationSSH string = "ssh" TelemetryApplicationSpeedtest string = "speedtest" @@ -77,9 +76,9 @@ func (b *TelemetryStore) updateDerpMap(cur *tailcfg.DERPMap) { n.IPv6 = ipv6 stunIP, _, _ := b.processIPLocked(n.STUNTestIP) n.STUNTestIP = stunIP - hn := b.hashAddr(n.HostName) + hn := b.hashAddrorHostname(n.HostName) n.HostName = hn - cn := b.hashAddr(n.CertName) + cn := b.hashAddrorHostname(n.CertName) n.CertName = cn } } @@ -140,7 +139,7 @@ func (b *TelemetryStore) toEndpoint(ipport string) *proto.TelemetryEvent_P2PEndp } addr := addrport.Addr() fields := addrToFields(addr) - hashStr := b.hashAddr(addr.String()) + hashStr := b.hashAddrorHostname(addr.String()) return &proto.TelemetryEvent_P2PEndpoint{ Hash: hashStr, Port: int32(addrport.Port()), @@ -159,11 +158,11 @@ func (b *TelemetryStore) processIPLocked(ip string) (string, *proto.IPFields, er } fields := addrToFields(addr) - hashStr := b.hashAddr(ip) + hashStr := b.hashAddrorHostname(ip) return hashStr, fields, nil } -func (b *TelemetryStore) hashAddr(addr string) string { +func (b *TelemetryStore) hashAddrorHostname(addr string) string { if hashStr, ok := b.hashCache[addr]; ok { return hashStr } diff --git a/tailnet/telemetry_internal_test.go b/tailnet/telemetry_internal_test.go index e92a09da91145..7abbe611d7d36 100644 --- a/tailnet/telemetry_internal_test.go +++ b/tailnet/telemetry_internal_test.go @@ -68,16 +68,18 @@ func TestTelemetryStore(t *testing.T) { event := telemetry.newEvent() + v4hash := telemetry.hashAddrorHostname(c.ipv4) require.Equal(t, &proto.Netcheck_NetcheckIP{ - Hash: telemetry.hashCache[c.ipv4], + Hash: v4hash, Fields: &proto.IPFields{ Version: 4, Class: c.expectedClass, }, }, event.LatestNetcheck.GlobalV4) + v6hash := telemetry.hashAddrorHostname(c.ipv6) require.Equal(t, &proto.Netcheck_NetcheckIP{ - Hash: telemetry.hashCache[c.ipv6], + Hash: v6hash, Fields: &proto.IPFields{ Version: 6, Class: c.expectedClass, @@ -95,9 +97,8 @@ func TestTelemetryStore(t *testing.T) { derpMap := &tailcfg.DERPMap{ Regions: make(map[int]*tailcfg.DERPRegion), } - // Add a region and node that uses every single field. - derpMap.Regions[999] = &tailcfg.DERPRegion{ - RegionID: 999, + derpMap.Regions[998] = &tailcfg.DERPRegion{ + RegionID: 998, EmbeddedRelay: true, RegionCode: "zzz", RegionName: "Cool Region", @@ -106,7 +107,24 @@ func TestTelemetryStore(t *testing.T) { Nodes: []*tailcfg.DERPNode{ { Name: "zzz1", - RegionID: 999, + RegionID: 998, + HostName: "coolderp.com", + CertName: "coolderpcert", + IPv4: "1.2.3.4", + IPv6: "2001:db8::1", + STUNTestIP: "5.6.7.8", + }, + }, + } + derpMap.Regions[999] = &tailcfg.DERPRegion{ + RegionID: 999, + EmbeddedRelay: true, + RegionCode: "zzo", + RegionName: "Other Cool Region", + Avoid: true, + Nodes: []*tailcfg.DERPNode{ + { + Name: "zzo1", HostName: "coolderp.com", CertName: "coolderpcert", IPv4: "1.2.3.4", @@ -124,5 +142,10 @@ func TestTelemetryStore(t *testing.T) { require.NotContains(t, node.Ipv4, "1.2.3.4") require.NotContains(t, node.Ipv6, "2001:db8::1") require.NotContains(t, node.StunTestIp, "5.6.7.8") + otherNode := event.DerpMap.Regions[998].Nodes[0] + require.Equal(t, otherNode.HostName, node.HostName) + require.Equal(t, otherNode.Ipv4, node.Ipv4) + require.Equal(t, otherNode.Ipv6, node.Ipv6) + require.Equal(t, otherNode.StunTestIp, node.StunTestIp) }) } From ffd62caf447d822055ca16b70087eb606169a351 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 3 Jul 2024 04:44:37 +0000 Subject: [PATCH 19/19] telemetry on disconnect --- cli/ssh.go | 1 + codersdk/workspacesdk/agentconn.go | 4 ++++ tailnet/conn.go | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/cli/ssh.go b/cli/ssh.go index 8c5dc7bdc46a2..9b853b704978c 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -437,6 +437,7 @@ func (r *RootCmd) ssh() *serpent.Command { } err = sshSession.Wait() + conn.SendDisconnectedTelemetry("ssh") if err != nil { if exitErr := (&gossh.ExitError{}); errors.As(err, &exitErr) { // Clear the error since it's not useful beyond diff --git a/codersdk/workspacesdk/agentconn.go b/codersdk/workspacesdk/agentconn.go index ed9da4c2a04bf..edd3584493bde 100644 --- a/codersdk/workspacesdk/agentconn.go +++ b/codersdk/workspacesdk/agentconn.go @@ -380,3 +380,7 @@ func (c *AgentConn) apiClient() *http.Client { func (c *AgentConn) GetPeerDiagnostics() tailnet.PeerDiagnostics { return c.Conn.GetPeerDiagnostics(c.opts.AgentID) } + +func (c *AgentConn) SendDisconnectedTelemetry(application string) { + c.Conn.SendDisconnectedTelemetry(c.agentAddress(), application) +} diff --git a/tailnet/conn.go b/tailnet/conn.go index de6269dda2d08..6c60dedfd22b5 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -727,6 +727,24 @@ func (c *Conn) SendConnectedTelemetry(ip netip.Addr, application string) { }() } +func (c *Conn) SendDisconnectedTelemetry(ip netip.Addr, application string) { + if c.telemetrySink == nil { + return + } + e := c.newTelemetryEvent() + e.Status = proto.TelemetryEvent_DISCONNECTED + e.Application = application + pip, ok := c.wireguardEngine.PeerForIP(ip) + if ok { + e.NodeIdRemote = uint64(pip.Node.ID) + } + c.telemetryWg.Add(1) + go func() { + defer c.telemetryWg.Done() + c.telemetrySink.SendTelemetryEvent(e) + }() +} + func (c *Conn) SendSpeedtestTelemetry(throughputMbits float64) { if c.telemetrySink == nil { return