diff --git a/cli/ping.go b/cli/ping.go index 82becb016bde7..644754283ee58 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.disableNetworkTelemetry { + 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..bab85464a9a01 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.disableNetworkTelemetry { + 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..418915490b910 100644 --- a/cli/root.go +++ b/cli/root.go @@ -52,19 +52,20 @@ var ( ) const ( - 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" + 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" + varDisableNetworkTelemetry = "disable-network-telemetry" notLoggedInMessage = "You are not logged in. Try logging in using 'coder login '." @@ -435,6 +436,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. 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, + }, { Flag: "debug-http", Description: "Debug codersdk HTTP requests.", @@ -481,8 +489,9 @@ type RootCmd struct { disableDirect bool debugHTTP 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 42fe7604c6dc4..c31fc8e65defc 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.disableNetworkTelemetry { + opts.EnableTelemetry = true + } if pcapFile != "" { s := capture.New() opts.CaptureHook = s.LogPacket @@ -183,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/cli/ssh.go b/cli/ssh.go index e4e9fadf5e8e8..9b853b704978c 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.disableNetworkTelemetry, }) if err != nil { return xerrors.Errorf("dial agent: %w", err) @@ -436,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/cli/testdata/coder_--help.golden b/cli/testdata/coder_--help.golden index e970347890eb2..ce220e95b1188 100644 --- a/cli/testdata/coder_--help.golden +++ b/cli/testdata/coder_--help.golden @@ -66,6 +66,13 @@ 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. 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/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index 3692d6eb5cbee..53055c686f72b 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), @@ -1221,33 +1221,28 @@ 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), } } // 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"` @@ -1281,11 +1276,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(), @@ -1296,9 +1286,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/agentconn.go b/codersdk/workspacesdk/agentconn.go index 6700f5d935273..edd3584493bde 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) @@ -378,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/codersdk/workspacesdk/connector.go b/codersdk/workspacesdk/connector.go index 5ac009af15091..44bcd46699ac4 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" @@ -38,6 +42,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,32 +63,32 @@ 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{} + + // Only set to true if we get a response from the server that it doesn't support + // network telemetry. + telemetryUnavailable atomic.Bool } -// 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 +104,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 +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) @@ -194,7 +208,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 +275,22 @@ 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 || tac.telemetryUnavailable.Load() { + return + } + ctx, cancel := context.WithTimeout(tac.ctx, 5*time.Second) + defer cancel() + _, 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.telemetryUnavailable.Store(true) + } +} diff --git a/codersdk/workspacesdk/connector_internal_test.go b/codersdk/workspacesdk/connector_internal_test.go index 2e5716ee17870..00463d2076016 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" @@ -75,7 +78,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) @@ -127,7 +131,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 @@ -137,6 +142,144 @@ 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) +} + +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()) + 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) { + 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()) + 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{} func (*fakeTailnetConn) UpdatePeers([]*proto.CoordinateResponse_PeerUpdate) error { @@ -155,3 +298,123 @@ func (*fakeTailnetConn) SetTunnelDestination(uuid.UUID) {} func newFakeTailnetConn() *fakeTailnetConn { return &fakeTailnetConn{} } + +type fakeDRPCClient struct { + postTelemetryCalls int64 + telemeteryErorr error + fakeDRPPCMapStream +} + +var _ proto.DRPCTailnetClient = &fakeDRPCClient{} + +func newFakeDRPCClient() *fakeDRPCClient { + return &fakeDRPCClient{ + postTelemetryCalls: 0, + 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, _ *proto.TelemetryRequest) (*proto.TelemetryResponse, error) { + atomic.AddInt64(&f.postTelemetryCalls, 1) + 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/codersdk/workspacesdk/workspacesdk.go b/codersdk/workspacesdk/workspacesdk.go index 04765c13d9877..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 @@ -181,6 +182,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 (in tests). + EnableTelemetry bool } func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options *DialAgentOptions) (agentConn *AgentConn, err error) { @@ -196,29 +200,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 +232,44 @@ 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, + ClientType: proto.TelemetryEvent_CLI, + 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..7f5364048e3ed 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -149,6 +149,18 @@ Enable verbose output. Disable direct (P2P) connections to workspaces. +### --disable-network-telemetry + +| | | +| ----------- | --------------------------------------------- | +| Type | bool | +| Environment | $CODER_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 7c2ff5c835dff..e575451922a5b 100644 --- a/enterprise/cli/testdata/coder_--help.golden +++ b/enterprise/cli/testdata/coder_--help.golden @@ -30,6 +30,13 @@ 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. 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/flake.nix b/flake.nix index ee6fbca7bd923..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-e0L6osJwG0EF0M3TefxaAjDvN4jvQHxTGEUEECNO1Vw="; + vendorHash = "sha256-5R2FelgM9NRYlse309NukEVh25pvusO2FXZx1VuWGoo="; 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/configmaps.go b/tailnet/configmaps.go index e6258817afaa7..5d609b90c4bd8 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 8b82c455e4788..6c60dedfd22b5 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -15,6 +15,8 @@ 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/wrapperspb" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "tailscale.com/envknob" @@ -99,6 +101,17 @@ 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 +} + +// 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 @@ -122,6 +135,15 @@ func NewConn(options *Options) (conn *Conn, err error) { return nil, xerrors.New("At least one IP range must be provided") } + var telemetryStore *TelemetryStore + if options.TelemetrySink != nil { + var err error + telemetryStore, err = newTelemetryStore() + if err != nil { + return nil, xerrors.Errorf("create telemetry store: %w", err) + } + } + nodePrivateKey := key.NewNode() var nodeID tailcfg.NodeID @@ -241,10 +263,18 @@ 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 telemetryStore != nil { + wireguardEngine.SetNetInfoCallback(func(ni *tailcfg.NetInfo) { + nodeUp.setNetInfo(ni) + telemetryStore.setNetInfo(ni) + }) + } else { + wireguardEngine.SetNetInfoCallback(nodeUp.setNetInfo) + } server := &Conn{ + id: uuid.New(), closed: make(chan struct{}), logger: options.Logger, magicConn: magicConn, @@ -259,6 +289,8 @@ func NewConn(options *Options) (conn *Conn, err error) { wireguardEngine: wireguardEngine, configMaps: cfgMaps, nodeUpdater: nodeUp, + telemetrySink: options.TelemetrySink, + telemeteryStore: telemetryStore, } defer func() { if err != nil { @@ -302,6 +334,8 @@ func IPFromUUID(uid uuid.UUID) netip.Addr { // Conn is an actively listening Wireguard connection. type Conn struct { + // Unique ID used for telemetry. + id uuid.UUID mutex sync.Mutex closed chan struct{} logger slog.Logger @@ -316,6 +350,12 @@ type Conn struct { wireguardRouter *router.Config wireguardEngine wgengine.Engine listeners map[listenKey]*listener + clientType proto.TelemetryEvent_ClientType + + telemetrySink TelemetrySink + // telemeteryStore will be nil if telemetrySink is nil. + telemeteryStore *TelemetryStore + telemetryWg sync.WaitGroup trafficStats *connstats.Statistics } @@ -350,7 +390,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.telemeteryStore != nil { + c.telemeteryStore.updateDerpMap(derpMap) + } } func (c *Conn) SetDERPForceWebSockets(v bool) { @@ -399,7 +441,11 @@ 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 { + c.sendPingTelemetry(pr) + } + return dur, p2p, pr, err } func (c *Conn) pingWithType(ctx context.Context, ip netip.Addr, pt tailcfg.PingType) (time.Duration, bool, *ipnstate.PingResult, error) { @@ -494,6 +540,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() @@ -662,6 +709,91 @@ func (c *Conn) MagicsockServeHTTPDebug(w http.ResponseWriter, r *http.Request) { c.magicConn.ServeHTTPDebug(w, r) } +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) + } + c.telemetryWg.Add(1) + go func() { + defer c.telemetryWg.Done() + c.telemetrySink.SendTelemetryEvent(e) + }() +} + +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 + } + e := c.newTelemetryEvent() + 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) + }() +} + +// nolint:revive +func (c *Conn) sendPingTelemetry(pr *ipnstate.PingResult) { + if c.telemetrySink == nil { + return + } + e := c.newTelemetryEvent() + + latency := durationpb.New(time.Duration(pr.LatencySeconds * float64(time.Second))) + if pr.Endpoint != "" { + e.P2PLatency = latency + e.P2PEndpoint = c.telemeteryStore.toEndpoint(pr.Endpoint) + } else { + e.DerpLatency = latency + } + e.Status = proto.TelemetryEvent_CONNECTED + c.telemetryWg.Add(1) + go func() { + defer c.telemetryWg.Done() + c.telemetrySink.SendTelemetryEvent(e) + }() +} + +// 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.newEvent() + event.ClientType = c.clientType + event.Id = id + selfNode := c.Node() + event.NodeIdSelf = uint64(selfNode.ID) + event.HomeDerp = strconv.Itoa(selfNode.PreferredDERP) + return event +} + // PeerDiagnostics is a checklist of human-readable conditions necessary to establish an encrypted // tunnel to a peer via a Conn type PeerDiagnostics struct { @@ -730,8 +862,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/proto/tailnet.pb.go b/tailnet/proto/tailnet.pb.go index a9268f04d992c..f777b956beffa 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, } ) @@ -643,20 +640,18 @@ 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"` + 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"` 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() { @@ -726,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 { @@ -782,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 @@ -817,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 @@ -838,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() { @@ -946,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 @@ -960,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 @@ -1651,7 +1616,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) } @@ -1664,7 +1629,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 { @@ -1677,7 +1642,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 { @@ -1707,7 +1672,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) } @@ -1720,7 +1685,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 { @@ -1930,218 +1895,191 @@ 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, + 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, 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, + 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, 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, 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, + 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, - 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, 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, + 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, - 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, + 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, 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, + 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, 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, 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, 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, + 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, 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, + 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, 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, + 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, 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, + 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, 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, + 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 ( @@ -2157,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, 28) +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 @@ -2185,21 +2123,19 @@ 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 + (*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 - 32, // 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 @@ -2209,54 +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 - 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 + 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 + 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 + 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() } @@ -2481,7 +2413,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 @@ -2493,7 +2425,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 @@ -2512,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: 28, + NumMessages: 26, NumExtensions: 0, NumServices: 1, }, diff --git a/tailnet/proto/tailnet.proto b/tailnet/proto/tailnet.proto index b8e97d8a7a493..6d025b1eb1749 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; } @@ -120,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; @@ -131,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; @@ -141,8 +139,6 @@ message Netcheck { } NetcheckIP GlobalV4 = 17; NetcheckIP GlobalV6 = 18; - - google.protobuf.BoolValue CaptivePortal = 19; } message TelemetryEvent { @@ -173,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 new file mode 100644 index 0000000000000..b8012e33a1ad4 --- /dev/null +++ b/tailnet/telemetry.go @@ -0,0 +1,198 @@ +package tailnet + +import ( + "crypto/sha256" + "encoding/hex" + "net/netip" + "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" + + "github.com/coder/coder/v2/cryptorand" + "github.com/coder/coder/v2/tailnet/proto" +) + +const ( + TelemetryApplicationSSH string = "ssh" + TelemetryApplicationSpeedtest string = "speedtest" +) + +// Responsible for storing and anonymizing networking telemetry state. +type TelemetryStore struct { + mu sync.Mutex + hashSalt string + // A cache to avoid hashing the same IP or hostname multiple times. + hashCache map[string]string + + cleanDerpMap *tailcfg.DERPMap + cleanNetCheck *proto.Netcheck +} + +func newTelemetryStore() (*TelemetryStore, error) { + hashSalt, err := cryptorand.String(16) + if err != nil { + return nil, err + } + return &TelemetryStore{ + hashSalt: hashSalt, + hashCache: make(map[string]string), + }, nil +} + +// newEvent returns the current telemetry state as an event +func (b *TelemetryStore) newEvent() *proto.TelemetryEvent { + b.mu.Lock() + defer b.mu.Unlock() + + return &proto.TelemetryEvent{ + Time: timestamppb.Now(), + DerpMap: DERPMapToProto(b.cleanDerpMap), + LatestNetcheck: b.cleanNetCheck, + + // TODO(ethanndickson): + ConnectionAge: &durationpb.Duration{}, + ConnectionSetup: &durationpb.Duration{}, + P2PSetup: &durationpb.Duration{}, + } +} + +// 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() + cleanMap := cur.Clone() + for _, r := range cleanMap.Regions { + for _, n := range r.Nodes { + 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.hashAddrorHostname(n.HostName) + n.HostName = hn + cn := b.hashAddrorHostname(n.CertName) + n.CertName = cn + } + } + 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.cleanNetCheck = &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.cleanNetCheck.GlobalV4 = &proto.Netcheck_NetcheckIP{ + Hash: v4hash, + Fields: v4fields, + } + } + v6hash, v6fields, err := b.processIPLocked(ni.GlobalV6) + if err == nil { + b.cleanNetCheck.GlobalV6 = &proto.Netcheck_NetcheckIP{ + Hash: v6hash, + Fields: v6fields, + } + } + for rid, seconds := range ni.DERPLatencyV4 { + b.cleanNetCheck.RegionV4Latency[int64(rid)] = durationpb.New(time.Duration(seconds * float64(time.Second))) + } + for rid, seconds := range ni.DERPLatencyV6 { + b.cleanNetCheck.RegionV6Latency[int64(rid)] = durationpb.New(time.Duration(seconds * float64(time.Second))) + } +} + +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.hashAddrorHostname(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. +// +// b.mu must be held. +func (b *TelemetryStore) processIPLocked(ip string) (string, *proto.IPFields, error) { + addr, err := netip.ParseAddr(ip) + if err != nil { + return "", nil, xerrors.Errorf("failed to parse IP %q: %w", ip, err) + } + + fields := addrToFields(addr) + hashStr := b.hashAddrorHostname(ip) + return hashStr, fields, nil +} + +func (b *TelemetryStore) hashAddrorHostname(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 + } + + 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 + } + + return &proto.IPFields{ + Version: version, + Class: class, + } +} diff --git a/tailnet/telemetry_internal_test.go b/tailnet/telemetry_internal_test.go new file mode 100644 index 0000000000000..7abbe611d7d36 --- /dev/null +++ b/tailnet/telemetry_internal_test.go @@ -0,0 +1,151 @@ +package tailnet + +import ( + "testing" + + "github.com/stretchr/testify/require" + "tailscale.com/tailcfg" + + "github.com/coder/coder/v2/tailnet/proto" +) + +func TestTelemetryStore(t *testing.T) { + t.Parallel() + + t.Run("CleanIPs", func(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + ipv4 string + ipv6 string + expectedVersion int32 + expectedClass proto.IPFields_IPClass + }{ + { + name: "Public", + ipv4: "142.250.71.78", + ipv6: "2404:6800:4006:812::200e", + expectedClass: proto.IPFields_PUBLIC, + }, + { + name: "Private", + ipv4: "192.168.0.1", + ipv6: "fd12:3456:789a:1::1", + expectedClass: proto.IPFields_PRIVATE, + }, + { + name: "LinkLocal", + ipv4: "169.254.1.1", + ipv6: "fe80::1", + expectedClass: proto.IPFields_LINK_LOCAL, + }, + { + name: "Loopback", + ipv4: "127.0.0.1", + ipv6: "::1", + expectedClass: proto.IPFields_LOOPBACK, + }, + { + name: "IPv4Mapped", + ipv4: "1.2.3.4", + ipv6: "::ffff:1.2.3.4", + expectedClass: proto.IPFields_PUBLIC, + }, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + telemetry, err := newTelemetryStore() + require.NoError(t, err) + + telemetry.setNetInfo(&tailcfg.NetInfo{ + GlobalV4: c.ipv4, + GlobalV6: c.ipv6, + }) + + event := telemetry.newEvent() + + v4hash := telemetry.hashAddrorHostname(c.ipv4) + require.Equal(t, &proto.Netcheck_NetcheckIP{ + 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: v6hash, + Fields: &proto.IPFields{ + Version: 6, + Class: c.expectedClass, + }, + }, event.LatestNetcheck.GlobalV6) + }) + } + }) + + t.Run("DerpMapClean", func(t *testing.T) { + t.Parallel() + telemetry, err := newTelemetryStore() + require.NoError(t, err) + + derpMap := &tailcfg.DERPMap{ + Regions: make(map[int]*tailcfg.DERPRegion), + } + derpMap.Regions[998] = &tailcfg.DERPRegion{ + RegionID: 998, + EmbeddedRelay: true, + RegionCode: "zzz", + RegionName: "Cool Region", + Avoid: true, + + Nodes: []*tailcfg.DERPNode{ + { + Name: "zzz1", + 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", + IPv6: "2001:db8::1", + STUNTestIP: "5.6.7.8", + }, + }, + } + telemetry.updateDerpMap(derpMap) + + 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") + 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) + }) +}