From 1c287b2f54e97c31d66a99f79416f2a11313620b Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Wed, 27 Mar 2024 22:44:37 +0000 Subject: [PATCH 01/14] feat: add agent acks to in-memory coordinator When an agent receives a node, it responds with an ACK which is relayed to the client. After the client receives the ACK, it's allowed to begin pinging. --- codersdk/workspacesdk/connector.go | 14 + ...nal_test.go => connector_internal_test.go} | 53 ++++ codersdk/workspacesdk/workspacesdk.go | 13 + tailnet/coordinator.go | 88 +++++- tailnet/coordinator_test.go | 73 +++++ tailnet/proto/tailnet.pb.go | 287 ++++++++++++++---- tailnet/proto/tailnet.proto | 13 + 7 files changed, 470 insertions(+), 71 deletions(-) rename codersdk/workspacesdk/{workspacesdk_internal_test.go => connector_internal_test.go} (64%) diff --git a/codersdk/workspacesdk/connector.go b/codersdk/workspacesdk/connector.go index 5c1d9e600aede..c9e6c5b616614 100644 --- a/codersdk/workspacesdk/connector.go +++ b/codersdk/workspacesdk/connector.go @@ -53,6 +53,8 @@ type tailnetAPIConnector struct { coordinateURL string dialOptions *websocket.DialOptions conn tailnetConn + agentAckOnce sync.Once + agentAck chan struct{} connected chan error isFirst bool @@ -74,6 +76,7 @@ func runTailnetAPIConnector( conn: conn, connected: make(chan error, 1), closed: make(chan struct{}), + agentAck: make(chan struct{}), } tac.gracefulCtx, tac.cancelGracefulCtx = context.WithCancel(context.Background()) go tac.manageGracefulTimeout() @@ -190,6 +193,17 @@ func (tac *tailnetAPIConnector) coordinate(client proto.DRPCTailnetClient) { }() coordination := tailnet.NewRemoteCoordination(tac.logger, coord, tac.conn, tac.agentID) tac.logger.Debug(tac.ctx, "serving coordinator") + go func() { + select { + case <-tac.ctx.Done(): + tac.logger.Debug(tac.ctx, "ctx timeout before agent ack") + case <-coordination.AwaitAck(): + tac.logger.Debug(tac.ctx, "got agent ack") + tac.agentAckOnce.Do(func() { + close(tac.agentAck) + }) + } + }() select { case <-tac.ctx.Done(): tac.logger.Debug(tac.ctx, "main context canceled; do graceful disconnect") diff --git a/codersdk/workspacesdk/workspacesdk_internal_test.go b/codersdk/workspacesdk/connector_internal_test.go similarity index 64% rename from codersdk/workspacesdk/workspacesdk_internal_test.go rename to codersdk/workspacesdk/connector_internal_test.go index 57e6f751ff840..ff0a910bcae0a 100644 --- a/codersdk/workspacesdk/workspacesdk_internal_test.go +++ b/codersdk/workspacesdk/connector_internal_test.go @@ -89,6 +89,59 @@ func TestTailnetAPIConnector_Disconnects(t *testing.T) { require.NotNil(t, reqDisc.Disconnect) } +func TestTailnetAPIConnector_Ack(t *testing.T) { + t.Parallel() + testCtx := testutil.Context(t, testutil.WaitShort) + ctx, cancel := context.WithCancel(testCtx) + defer cancel() + 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) + svc, err := tailnet.NewClientService( + logger, &coordPtr, + time.Millisecond, func() *tailcfg.DERPMap { return <-derpMapCh }, + ) + 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 := runTailnetAPIConnector(ctx, logger, agentID, svr.URL, &websocket.DialOptions{}, fConn) + + call := testutil.RequireRecvCtx(ctx, t, fCoord.CoordinateCalls) + reqTun := testutil.RequireRecvCtx(ctx, t, call.Reqs) + require.NotNil(t, reqTun.AddTunnel) + + _ = testutil.RequireRecvCtx(ctx, t, uut.connected) + + // send an ack to the client + testutil.RequireSendCtx(ctx, t, call.Resps, &proto.CoordinateResponse{ + TunnelAck: &proto.CoordinateResponse_Ack{Id: agentID[:]}, + }) + + // the agentAck channel should be successfully closed + _ = testutil.RequireRecvCtx(ctx, t, uut.agentAck) +} + type fakeTailnetConn struct{} func (*fakeTailnetConn) UpdatePeers([]*proto.CoordinateResponse_PeerUpdate) error { diff --git a/codersdk/workspacesdk/workspacesdk.go b/codersdk/workspacesdk/workspacesdk.go index 8af29f8f76f1f..74364dc03952e 100644 --- a/codersdk/workspacesdk/workspacesdk.go +++ b/codersdk/workspacesdk/workspacesdk.go @@ -260,6 +260,19 @@ func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options * options.Logger.Debug(ctx, "connected to tailnet v2+ API") } + // TODO: uncomment after pgcoord ack's are implemented (upstack pr) + // options.Logger.Debug(ctx, "waiting for agent ack") + // // 5 seconds is chosen because this is the timeout for failed Wireguard + // // handshakes. In the worst case, we wait the same amount of time as a + // // failed handshake. + // timer := time.NewTimer(5 * time.Second) + // select { + // case <-connector.agentAck: + // case <-timer.C: + // options.Logger.Debug(ctx, "timed out waiting for agent ack") + // } + // timer.Stop() + agentConn = NewAgentConn(conn, AgentConnOptions{ AgentID: agentID, CloseFunc: func() error { diff --git a/tailnet/coordinator.go b/tailnet/coordinator.go index ce9c8e99b29c0..f5025177bc255 100644 --- a/tailnet/coordinator.go +++ b/tailnet/coordinator.go @@ -102,6 +102,7 @@ type Coordinatee interface { } type Coordination interface { + AwaitAck() <-chan struct{} io.Closer Error() <-chan error } @@ -111,9 +112,14 @@ type remoteCoordination struct { closed bool errChan chan error coordinatee Coordinatee + tgt uuid.UUID logger slog.Logger protocol proto.DRPCTailnet_CoordinateClient respLoopDone chan struct{} + + ackOnce sync.Once + // tgtAck is closed when an ack from tgt is received. + tgtAck chan struct{} } func (c *remoteCoordination) Close() (retErr error) { @@ -161,14 +167,54 @@ func (c *remoteCoordination) respLoop() { c.sendErr(xerrors.Errorf("read: %w", err)) return } - err = c.coordinatee.UpdatePeers(resp.GetPeerUpdates()) - if err != nil { - c.sendErr(xerrors.Errorf("update peers: %w", err)) - return + + if len(resp.GetPeerUpdates()) > 0 { + err = c.coordinatee.UpdatePeers(resp.GetPeerUpdates()) + if err != nil { + c.sendErr(xerrors.Errorf("update peers: %w", err)) + return + } + + // Only send acks from agents. + if c.tgt == uuid.Nil { + // Send an ack back for all received peers. This could + // potentially be smarter to only send an ACK once per client, + // but there's nothing currently stopping clients from reusing + // IDs. + for _, peer := range resp.GetPeerUpdates() { + err := c.protocol.Send(&proto.CoordinateRequest{ + TunnelAck: &proto.CoordinateRequest_Ack{Id: peer.Id}, + }) + if err != nil { + c.sendErr(xerrors.Errorf("send: %w", err)) + return + } + } + } + } + + // If we receive an ack, close the tgtAck channel to notify the waiting + // client. + if ack := resp.GetTunnelAck(); ack != nil { + dstID, err := uuid.FromBytes(ack.Id) + if err != nil { + c.sendErr(xerrors.Errorf("parse ack id: %w", err)) + return + } + + if c.tgt == dstID { + c.ackOnce.Do(func() { + close(c.tgtAck) + }) + } } } } +func (c *remoteCoordination) AwaitAck() <-chan struct{} { + return c.tgtAck +} + // NewRemoteCoordination uses the provided protocol to coordinate the provided coordinatee (usually a // Conn). If the tunnelTarget is not uuid.Nil, then we add a tunnel to the peer (i.e. we are acting as // a client---agents should NOT set this!). @@ -179,9 +225,11 @@ func NewRemoteCoordination(logger slog.Logger, c := &remoteCoordination{ errChan: make(chan error, 1), coordinatee: coordinatee, + tgt: tunnelTarget, logger: logger, protocol: protocol, respLoopDone: make(chan struct{}), + tgtAck: make(chan struct{}), } if tunnelTarget != uuid.Nil { c.Lock() @@ -327,6 +375,13 @@ func (c *inMemoryCoordination) respLoop() { } } +func (*inMemoryCoordination) AwaitAck() <-chan struct{} { + // This is only used for tests, so just return a closed channel. + ch := make(chan struct{}) + close(ch) + return ch +} + func (c *inMemoryCoordination) Close() error { c.Lock() defer c.Unlock() @@ -658,6 +713,31 @@ func (c *core) handleRequest(p *peer, req *proto.CoordinateRequest) error { if req.Disconnect != nil { c.removePeerLocked(p.id, proto.CoordinateResponse_PeerUpdate_DISCONNECTED, "graceful disconnect") } + if ack := req.TunnelAck; ack != nil { + err := c.handleAckLocked(pr, ack) + if err != nil { + return xerrors.Errorf("handle ack: %w", err) + } + } + return nil +} + +func (c *core) handleAckLocked(src *peer, ack *proto.CoordinateRequest_Ack) error { + dstID, err := uuid.FromBytes(ack.Id) + if err != nil { + // this shouldn't happen unless there is a client error. Close the connection so the client + // doesn't just happily continue thinking everything is fine. + return xerrors.Errorf("unable to convert bytes to UUID: %w", err) + } + + dst, ok := c.peers[dstID] + if ok { + dst.resps <- &proto.CoordinateResponse{ + TunnelAck: &proto.CoordinateResponse_Ack{ + Id: src.id[:], + }, + } + } return nil } diff --git a/tailnet/coordinator_test.go b/tailnet/coordinator_test.go index d8a6f297b539d..ca89b03180616 100644 --- a/tailnet/coordinator_test.go +++ b/tailnet/coordinator_test.go @@ -412,6 +412,24 @@ func TestCoordinator(t *testing.T) { _ = testutil.RequireRecvCtx(ctx, t, clientErrChan) _ = testutil.RequireRecvCtx(ctx, t, closeClientChan) }) + + t.Run("AgentAck", func(t *testing.T) { + t.Parallel() + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + coordinator := tailnet.NewCoordinator(logger) + ctx := testutil.Context(t, testutil.WaitShort) + + clientID := uuid.New() + agentID := uuid.New() + + aReq, _ := coordinator.Coordinate(ctx, agentID, agentID.String(), tailnet.AgentCoordinateeAuth{ID: agentID}) + _, cRes := coordinator.Coordinate(ctx, clientID, clientID.String(), tailnet.ClientCoordinateeAuth{AgentID: agentID}) + + aReq <- &proto.CoordinateRequest{TunnelAck: &proto.CoordinateRequest_Ack{Id: clientID[:]}} + ack := testutil.RequireRecvCtx(ctx, t, cRes) + require.NotNil(t, ack.TunnelAck) + require.Equal(t, agentID[:], ack.TunnelAck.Id) + }) } // TestCoordinator_AgentUpdateWhileClientConnects tests for regression on @@ -638,6 +656,61 @@ func TestRemoteCoordination(t *testing.T) { } } +func TestRemoteCoordination_Ack(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + clientID := uuid.UUID{1} + agentID := uuid.UUID{2} + mCoord := tailnettest.NewMockCoordinator(gomock.NewController(t)) + fConn := &fakeCoordinatee{} + + reqs := make(chan *proto.CoordinateRequest, 100) + resps := make(chan *proto.CoordinateResponse, 100) + mCoord.EXPECT().Coordinate(gomock.Any(), clientID, gomock.Any(), tailnet.ClientCoordinateeAuth{agentID}). + Times(1).Return(reqs, resps) + + var coord tailnet.Coordinator = mCoord + coordPtr := atomic.Pointer[tailnet.Coordinator]{} + coordPtr.Store(&coord) + svc, err := tailnet.NewClientService( + logger.Named("svc"), &coordPtr, + time.Hour, + func() *tailcfg.DERPMap { panic("not implemented") }, + ) + require.NoError(t, err) + sC, cC := net.Pipe() + + serveErr := make(chan error, 1) + go func() { + err := svc.ServeClient(ctx, proto.CurrentVersion.String(), sC, clientID, agentID) + serveErr <- err + }() + + client, err := tailnet.NewDRPCClient(cC, logger) + require.NoError(t, err) + protocol, err := client.Coordinate(ctx) + require.NoError(t, err) + + uut := tailnet.NewRemoteCoordination(logger.Named("coordination"), protocol, fConn, agentID) + defer uut.Close() + + testutil.RequireSendCtx(ctx, t, resps, &proto.CoordinateResponse{ + TunnelAck: &proto.CoordinateResponse_Ack{Id: agentID[:]}, + }) + + testutil.RequireRecvCtx(ctx, t, uut.AwaitAck()) + + require.NoError(t, uut.Close()) + + select { + case err := <-uut.Error(): + require.ErrorContains(t, err, "stream terminated by sending close") + default: + // OK! + } +} + // coordinationTest tests that a coordination behaves correctly func coordinationTest( ctx context.Context, t *testing.T, diff --git a/tailnet/proto/tailnet.pb.go b/tailnet/proto/tailnet.pb.go index 63444f2173d60..6b1b09d6495e3 100644 --- a/tailnet/proto/tailnet.pb.go +++ b/tailnet/proto/tailnet.pb.go @@ -295,6 +295,7 @@ type CoordinateRequest struct { Disconnect *CoordinateRequest_Disconnect `protobuf:"bytes,2,opt,name=disconnect,proto3" json:"disconnect,omitempty"` AddTunnel *CoordinateRequest_Tunnel `protobuf:"bytes,3,opt,name=add_tunnel,json=addTunnel,proto3" json:"add_tunnel,omitempty"` RemoveTunnel *CoordinateRequest_Tunnel `protobuf:"bytes,4,opt,name=remove_tunnel,json=removeTunnel,proto3" json:"remove_tunnel,omitempty"` + TunnelAck *CoordinateRequest_Ack `protobuf:"bytes,5,opt,name=tunnel_ack,json=tunnelAck,proto3" json:"tunnel_ack,omitempty"` } func (x *CoordinateRequest) Reset() { @@ -357,12 +358,20 @@ func (x *CoordinateRequest) GetRemoveTunnel() *CoordinateRequest_Tunnel { return nil } +func (x *CoordinateRequest) GetTunnelAck() *CoordinateRequest_Ack { + if x != nil { + return x.TunnelAck + } + return nil +} + type CoordinateResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields PeerUpdates []*CoordinateResponse_PeerUpdate `protobuf:"bytes,1,rep,name=peer_updates,json=peerUpdates,proto3" json:"peer_updates,omitempty"` + TunnelAck *CoordinateResponse_Ack `protobuf:"bytes,2,opt,name=tunnel_ack,json=tunnelAck,proto3" json:"tunnel_ack,omitempty"` } func (x *CoordinateResponse) Reset() { @@ -404,6 +413,13 @@ func (x *CoordinateResponse) GetPeerUpdates() []*CoordinateResponse_PeerUpdate { return nil } +func (x *CoordinateResponse) GetTunnelAck() *CoordinateResponse_Ack { + if x != nil { + return x.TunnelAck + } + return nil +} + type DERPMap_HomeParams struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -813,6 +829,56 @@ func (x *CoordinateRequest_Tunnel) GetId() []byte { return nil } +// Acks are sent from agents back to the client, acknowledging receipt of +// the client's node. If the client starts pinging before an ACK, the +// handshake will likely be dropped. +type CoordinateRequest_Ack struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *CoordinateRequest_Ack) Reset() { + *x = CoordinateRequest_Ack{} + if protoimpl.UnsafeEnabled { + mi := &file_tailnet_proto_tailnet_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CoordinateRequest_Ack) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CoordinateRequest_Ack) ProtoMessage() {} + +func (x *CoordinateRequest_Ack) ProtoReflect() protoreflect.Message { + mi := &file_tailnet_proto_tailnet_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CoordinateRequest_Ack.ProtoReflect.Descriptor instead. +func (*CoordinateRequest_Ack) Descriptor() ([]byte, []int) { + return file_tailnet_proto_tailnet_proto_rawDescGZIP(), []int{3, 3} +} + +func (x *CoordinateRequest_Ack) GetId() []byte { + if x != nil { + return x.Id + } + return nil +} + type CoordinateResponse_PeerUpdate struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -827,7 +893,7 @@ type CoordinateResponse_PeerUpdate struct { func (x *CoordinateResponse_PeerUpdate) Reset() { *x = CoordinateResponse_PeerUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_tailnet_proto_tailnet_proto_msgTypes[15] + mi := &file_tailnet_proto_tailnet_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -840,7 +906,7 @@ func (x *CoordinateResponse_PeerUpdate) String() string { func (*CoordinateResponse_PeerUpdate) ProtoMessage() {} func (x *CoordinateResponse_PeerUpdate) ProtoReflect() protoreflect.Message { - mi := &file_tailnet_proto_tailnet_proto_msgTypes[15] + mi := &file_tailnet_proto_tailnet_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -884,6 +950,53 @@ func (x *CoordinateResponse_PeerUpdate) GetReason() string { return "" } +type CoordinateResponse_Ack struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *CoordinateResponse_Ack) Reset() { + *x = CoordinateResponse_Ack{} + if protoimpl.UnsafeEnabled { + mi := &file_tailnet_proto_tailnet_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CoordinateResponse_Ack) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CoordinateResponse_Ack) ProtoMessage() {} + +func (x *CoordinateResponse_Ack) ProtoReflect() protoreflect.Message { + mi := &file_tailnet_proto_tailnet_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CoordinateResponse_Ack.ProtoReflect.Descriptor instead. +func (*CoordinateResponse_Ack) Descriptor() ([]byte, []int) { + return file_tailnet_proto_tailnet_proto_rawDescGZIP(), []int{4, 1} +} + +func (x *CoordinateResponse_Ack) GetId() []byte { + if x != nil { + return x.Id + } + return nil +} + var File_tailnet_proto_tailnet_proto protoreflect.FileDescriptor var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ @@ -992,7 +1105,7 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb2, 0x03, 0x0a, 0x11, 0x43, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x91, 0x04, 0x0a, 0x11, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, @@ -1013,50 +1126,62 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x01, 0x28, 0x0b, 0x32, 0x2a, 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, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, - 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x1a, 0x38, 0x0a, - 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x6c, 0x66, 0x12, 0x2a, 0x0a, 0x04, 0x6e, - 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, - 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x1a, 0x0c, 0x0a, 0x0a, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x1a, 0x18, 0x0a, 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, - 0xd9, 0x02, 0x0a, 0x12, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 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, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x70, - 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x1a, 0xee, 0x01, 0x0a, 0x0a, 0x50, - 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x6f, 0x64, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, - 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x48, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x34, 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, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, - 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x42, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, - 0x14, 0x0a, 0x10, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x44, 0x45, 0x10, 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, 0x32, 0xbe, 0x01, 0x0a, 0x07, - 0x54, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 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, + 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x46, 0x0a, + 0x0a, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x61, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x63, 0x6b, 0x52, 0x09, 0x74, 0x75, 0x6e, 0x6e, + 0x65, 0x6c, 0x41, 0x63, 0x6b, 0x1a, 0x38, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, + 0x65, 0x6c, 0x66, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x1a, + 0x0c, 0x0a, 0x0a, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x1a, 0x18, 0x0a, + 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x1a, 0x15, 0x0a, 0x03, 0x41, 0x63, 0x6b, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, 0xb9, + 0x03, 0x0a, 0x12, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 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, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x70, 0x65, + 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x0a, 0x74, 0x75, 0x6e, + 0x6e, 0x65, 0x6c, 0x5f, 0x61, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, - 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, - 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x63, 0x6b, 0x52, 0x09, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x41, + 0x63, 0x6b, 0x1a, 0xee, 0x01, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x48, 0x0a, + 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x34, 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, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x4b, 0x69, 0x6e, + 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, + 0x42, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x10, 0x4b, 0x49, 0x4e, 0x44, 0x5f, + 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, + 0x04, 0x4e, 0x4f, 0x44, 0x45, 0x10, 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, 0x1a, 0x15, 0x0a, 0x03, 0x41, 0x63, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x32, 0xbe, 0x01, 0x0a, 0x07, 0x54, + 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x12, 0x56, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x30, 0x01, 0x12, 0x5b, + 0x0a, 0x0a, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1072,7 +1197,7 @@ func file_tailnet_proto_tailnet_proto_rawDescGZIP() []byte { } var file_tailnet_proto_tailnet_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_tailnet_proto_tailnet_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_tailnet_proto_tailnet_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_tailnet_proto_tailnet_proto_goTypes = []interface{}{ (CoordinateResponse_PeerUpdate_Kind)(0), // 0: coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind (*DERPMap)(nil), // 1: coder.tailnet.v2.DERPMap @@ -1090,35 +1215,39 @@ var file_tailnet_proto_tailnet_proto_goTypes = []interface{}{ (*CoordinateRequest_UpdateSelf)(nil), // 13: coder.tailnet.v2.CoordinateRequest.UpdateSelf (*CoordinateRequest_Disconnect)(nil), // 14: coder.tailnet.v2.CoordinateRequest.Disconnect (*CoordinateRequest_Tunnel)(nil), // 15: coder.tailnet.v2.CoordinateRequest.Tunnel - (*CoordinateResponse_PeerUpdate)(nil), // 16: coder.tailnet.v2.CoordinateResponse.PeerUpdate - (*timestamppb.Timestamp)(nil), // 17: google.protobuf.Timestamp + (*CoordinateRequest_Ack)(nil), // 16: coder.tailnet.v2.CoordinateRequest.Ack + (*CoordinateResponse_PeerUpdate)(nil), // 17: coder.tailnet.v2.CoordinateResponse.PeerUpdate + (*CoordinateResponse_Ack)(nil), // 18: coder.tailnet.v2.CoordinateResponse.Ack + (*timestamppb.Timestamp)(nil), // 19: google.protobuf.Timestamp } var file_tailnet_proto_tailnet_proto_depIdxs = []int32{ 6, // 0: coder.tailnet.v2.DERPMap.home_params:type_name -> coder.tailnet.v2.DERPMap.HomeParams 8, // 1: coder.tailnet.v2.DERPMap.regions:type_name -> coder.tailnet.v2.DERPMap.RegionsEntry - 17, // 2: coder.tailnet.v2.Node.as_of:type_name -> google.protobuf.Timestamp + 19, // 2: coder.tailnet.v2.Node.as_of:type_name -> google.protobuf.Timestamp 11, // 3: coder.tailnet.v2.Node.derp_latency:type_name -> coder.tailnet.v2.Node.DerpLatencyEntry 12, // 4: coder.tailnet.v2.Node.derp_forced_websocket:type_name -> coder.tailnet.v2.Node.DerpForcedWebsocketEntry 13, // 5: coder.tailnet.v2.CoordinateRequest.update_self:type_name -> coder.tailnet.v2.CoordinateRequest.UpdateSelf 14, // 6: coder.tailnet.v2.CoordinateRequest.disconnect:type_name -> coder.tailnet.v2.CoordinateRequest.Disconnect 15, // 7: coder.tailnet.v2.CoordinateRequest.add_tunnel:type_name -> coder.tailnet.v2.CoordinateRequest.Tunnel 15, // 8: coder.tailnet.v2.CoordinateRequest.remove_tunnel:type_name -> coder.tailnet.v2.CoordinateRequest.Tunnel - 16, // 9: coder.tailnet.v2.CoordinateResponse.peer_updates:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate - 9, // 10: coder.tailnet.v2.DERPMap.HomeParams.region_score:type_name -> coder.tailnet.v2.DERPMap.HomeParams.RegionScoreEntry - 10, // 11: coder.tailnet.v2.DERPMap.Region.nodes:type_name -> coder.tailnet.v2.DERPMap.Region.Node - 7, // 12: coder.tailnet.v2.DERPMap.RegionsEntry.value:type_name -> coder.tailnet.v2.DERPMap.Region - 3, // 13: coder.tailnet.v2.CoordinateRequest.UpdateSelf.node:type_name -> coder.tailnet.v2.Node - 3, // 14: coder.tailnet.v2.CoordinateResponse.PeerUpdate.node:type_name -> coder.tailnet.v2.Node - 0, // 15: coder.tailnet.v2.CoordinateResponse.PeerUpdate.kind:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind - 2, // 16: coder.tailnet.v2.Tailnet.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest - 4, // 17: coder.tailnet.v2.Tailnet.Coordinate:input_type -> coder.tailnet.v2.CoordinateRequest - 1, // 18: coder.tailnet.v2.Tailnet.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap - 5, // 19: coder.tailnet.v2.Tailnet.Coordinate:output_type -> coder.tailnet.v2.CoordinateResponse - 18, // [18:20] is the sub-list for method output_type - 16, // [16:18] is the sub-list for method input_type - 16, // [16:16] is the sub-list for extension type_name - 16, // [16:16] is the sub-list for extension extendee - 0, // [0:16] is the sub-list for field type_name + 16, // 9: coder.tailnet.v2.CoordinateRequest.tunnel_ack:type_name -> coder.tailnet.v2.CoordinateRequest.Ack + 17, // 10: coder.tailnet.v2.CoordinateResponse.peer_updates:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate + 18, // 11: coder.tailnet.v2.CoordinateResponse.tunnel_ack:type_name -> coder.tailnet.v2.CoordinateResponse.Ack + 9, // 12: coder.tailnet.v2.DERPMap.HomeParams.region_score:type_name -> coder.tailnet.v2.DERPMap.HomeParams.RegionScoreEntry + 10, // 13: coder.tailnet.v2.DERPMap.Region.nodes:type_name -> coder.tailnet.v2.DERPMap.Region.Node + 7, // 14: coder.tailnet.v2.DERPMap.RegionsEntry.value:type_name -> coder.tailnet.v2.DERPMap.Region + 3, // 15: coder.tailnet.v2.CoordinateRequest.UpdateSelf.node:type_name -> coder.tailnet.v2.Node + 3, // 16: coder.tailnet.v2.CoordinateResponse.PeerUpdate.node:type_name -> coder.tailnet.v2.Node + 0, // 17: coder.tailnet.v2.CoordinateResponse.PeerUpdate.kind:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind + 2, // 18: coder.tailnet.v2.Tailnet.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest + 4, // 19: coder.tailnet.v2.Tailnet.Coordinate:input_type -> coder.tailnet.v2.CoordinateRequest + 1, // 20: coder.tailnet.v2.Tailnet.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap + 5, // 21: coder.tailnet.v2.Tailnet.Coordinate:output_type -> coder.tailnet.v2.CoordinateResponse + 20, // [20:22] is the sub-list for method output_type + 18, // [18:20] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } func init() { file_tailnet_proto_tailnet_proto_init() } @@ -1260,6 +1389,18 @@ func file_tailnet_proto_tailnet_proto_init() { } } file_tailnet_proto_tailnet_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CoordinateRequest_Ack); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tailnet_proto_tailnet_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CoordinateResponse_PeerUpdate); i { case 0: return &v.state @@ -1271,6 +1412,18 @@ func file_tailnet_proto_tailnet_proto_init() { return nil } } + file_tailnet_proto_tailnet_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CoordinateResponse_Ack); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1278,7 +1431,7 @@ func file_tailnet_proto_tailnet_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_tailnet_proto_tailnet_proto_rawDesc, NumEnums: 1, - NumMessages: 16, + NumMessages: 18, NumExtensions: 0, NumServices: 1, }, diff --git a/tailnet/proto/tailnet.proto b/tailnet/proto/tailnet.proto index 83445e7579246..887d49afdf6a9 100644 --- a/tailnet/proto/tailnet.proto +++ b/tailnet/proto/tailnet.proto @@ -68,6 +68,14 @@ message CoordinateRequest { } Tunnel add_tunnel = 3; Tunnel remove_tunnel = 4; + + // Acks are sent from agents back to the client, acknowledging receipt of + // the client's node. If the client starts pinging before an ACK, the + // handshake will likely be dropped. + message Ack { + bytes id = 1; + } + Ack tunnel_ack = 5; } message CoordinateResponse { @@ -86,6 +94,11 @@ message CoordinateResponse { string reason = 4; } repeated PeerUpdate peer_updates = 1; + + message Ack { + bytes id = 1; + } + Ack tunnel_ack = 2; } service Tailnet { From 4b14af04542983ae694f7145ba7ea473c24659b4 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 29 Mar 2024 02:01:24 +0000 Subject: [PATCH 02/14] spike comments --- codersdk/workspacesdk/connector.go | 14 - .../workspacesdk/connector_internal_test.go | 53 ---- codersdk/workspacesdk/workspacesdk.go | 15 +- enterprise/coderd/workspaceproxy.go | 2 +- tailnet/configmaps.go | 98 ++++-- tailnet/configmaps_internal_test.go | 69 +++++ tailnet/conn.go | 5 +- tailnet/coordinator.go | 103 +++---- tailnet/coordinator_test.go | 33 +- tailnet/proto/tailnet.pb.go | 286 +++++++----------- tailnet/proto/tailnet.proto | 17 +- 11 files changed, 334 insertions(+), 361 deletions(-) diff --git a/codersdk/workspacesdk/connector.go b/codersdk/workspacesdk/connector.go index c9e6c5b616614..5c1d9e600aede 100644 --- a/codersdk/workspacesdk/connector.go +++ b/codersdk/workspacesdk/connector.go @@ -53,8 +53,6 @@ type tailnetAPIConnector struct { coordinateURL string dialOptions *websocket.DialOptions conn tailnetConn - agentAckOnce sync.Once - agentAck chan struct{} connected chan error isFirst bool @@ -76,7 +74,6 @@ func runTailnetAPIConnector( conn: conn, connected: make(chan error, 1), closed: make(chan struct{}), - agentAck: make(chan struct{}), } tac.gracefulCtx, tac.cancelGracefulCtx = context.WithCancel(context.Background()) go tac.manageGracefulTimeout() @@ -193,17 +190,6 @@ func (tac *tailnetAPIConnector) coordinate(client proto.DRPCTailnetClient) { }() coordination := tailnet.NewRemoteCoordination(tac.logger, coord, tac.conn, tac.agentID) tac.logger.Debug(tac.ctx, "serving coordinator") - go func() { - select { - case <-tac.ctx.Done(): - tac.logger.Debug(tac.ctx, "ctx timeout before agent ack") - case <-coordination.AwaitAck(): - tac.logger.Debug(tac.ctx, "got agent ack") - tac.agentAckOnce.Do(func() { - close(tac.agentAck) - }) - } - }() select { case <-tac.ctx.Done(): tac.logger.Debug(tac.ctx, "main context canceled; do graceful disconnect") diff --git a/codersdk/workspacesdk/connector_internal_test.go b/codersdk/workspacesdk/connector_internal_test.go index ff0a910bcae0a..57e6f751ff840 100644 --- a/codersdk/workspacesdk/connector_internal_test.go +++ b/codersdk/workspacesdk/connector_internal_test.go @@ -89,59 +89,6 @@ func TestTailnetAPIConnector_Disconnects(t *testing.T) { require.NotNil(t, reqDisc.Disconnect) } -func TestTailnetAPIConnector_Ack(t *testing.T) { - t.Parallel() - testCtx := testutil.Context(t, testutil.WaitShort) - ctx, cancel := context.WithCancel(testCtx) - defer cancel() - 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) - svc, err := tailnet.NewClientService( - logger, &coordPtr, - time.Millisecond, func() *tailcfg.DERPMap { return <-derpMapCh }, - ) - 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 := runTailnetAPIConnector(ctx, logger, agentID, svr.URL, &websocket.DialOptions{}, fConn) - - call := testutil.RequireRecvCtx(ctx, t, fCoord.CoordinateCalls) - reqTun := testutil.RequireRecvCtx(ctx, t, call.Reqs) - require.NotNil(t, reqTun.AddTunnel) - - _ = testutil.RequireRecvCtx(ctx, t, uut.connected) - - // send an ack to the client - testutil.RequireSendCtx(ctx, t, call.Resps, &proto.CoordinateResponse{ - TunnelAck: &proto.CoordinateResponse_Ack{Id: agentID[:]}, - }) - - // the agentAck channel should be successfully closed - _ = testutil.RequireRecvCtx(ctx, t, uut.agentAck) -} - type fakeTailnetConn struct{} func (*fakeTailnetConn) UpdatePeers([]*proto.CoordinateResponse_PeerUpdate) error { diff --git a/codersdk/workspacesdk/workspacesdk.go b/codersdk/workspacesdk/workspacesdk.go index 74364dc03952e..9d0a25173a038 100644 --- a/codersdk/workspacesdk/workspacesdk.go +++ b/codersdk/workspacesdk/workspacesdk.go @@ -203,6 +203,8 @@ func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options * DERPForceWebSockets: connInfo.DERPForceWebSockets, Logger: options.Logger, BlockEndpoints: c.client.DisableDirectConnections || options.BlockEndpoints, + // TODO: enable in upstack PR + // ShouldWaitForHandshake: true, }) if err != nil { return nil, xerrors.Errorf("create tailnet: %w", err) @@ -260,19 +262,6 @@ func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options * options.Logger.Debug(ctx, "connected to tailnet v2+ API") } - // TODO: uncomment after pgcoord ack's are implemented (upstack pr) - // options.Logger.Debug(ctx, "waiting for agent ack") - // // 5 seconds is chosen because this is the timeout for failed Wireguard - // // handshakes. In the worst case, we wait the same amount of time as a - // // failed handshake. - // timer := time.NewTimer(5 * time.Second) - // select { - // case <-connector.agentAck: - // case <-timer.C: - // options.Logger.Debug(ctx, "timed out waiting for agent ack") - // } - // timer.Stop() - agentConn = NewAgentConn(conn, AgentConnOptions{ AgentID: agentID, CloseFunc: func() error { diff --git a/enterprise/coderd/workspaceproxy.go b/enterprise/coderd/workspaceproxy.go index 379d01ad43018..234212f479cfd 100644 --- a/enterprise/coderd/workspaceproxy.go +++ b/enterprise/coderd/workspaceproxy.go @@ -658,7 +658,7 @@ func (api *API) workspaceProxyRegister(rw http.ResponseWriter, r *http.Request) if err != nil { return xerrors.Errorf("insert replica: %w", err) } - } else if err != nil { + } else { return xerrors.Errorf("get replica: %w", err) } diff --git a/tailnet/configmaps.go b/tailnet/configmaps.go index 57a2d9f2d1940..b6e69d3196f60 100644 --- a/tailnet/configmaps.go +++ b/tailnet/configmaps.go @@ -56,10 +56,11 @@ type phased struct { type configMaps struct { phased - netmapDirty bool - derpMapDirty bool - filterDirty bool - closing bool + netmapDirty bool + derpMapDirty bool + filterDirty bool + closing bool + waitForHandshake bool engine engineConfigurable static netmap.NetworkMap @@ -216,6 +217,9 @@ func (c *configMaps) netMapLocked() *netmap.NetworkMap { func (c *configMaps) peerConfigLocked() []*tailcfg.Node { out := make([]*tailcfg.Node, 0, len(c.peers)) for _, p := range c.peers { + if !p.readyForHandshake { + continue + } n := p.node.Clone() if c.blockEndpoints { n.Endpoints = nil @@ -225,6 +229,12 @@ func (c *configMaps) peerConfigLocked() []*tailcfg.Node { return out } +func (c *configMaps) setWaitForHandshake(wait bool) { + c.L.Lock() + defer c.L.Unlock() + c.waitForHandshake = wait +} + // setAddresses sets the addresses belonging to this node to the given slice. It // triggers configuration of the engine if the addresses have changed. // c.L MUST NOT be held. @@ -377,17 +387,9 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat return false } logger = logger.With(slog.F("key_id", node.Key.ShortString()), slog.F("node", node)) - peerStatus, ok := status.Peer[node.Key] - // Starting KeepAlive messages at the initialization of a connection - // causes a race condition. If we send the handshake before the peer has - // our node, we'll have to wait for 5 seconds before trying again. - // Ideally, the first handshake starts when the user first initiates a - // connection to the peer. After a successful connection we enable - // keep alives to persist the connection and keep it from becoming idle. - // SSH connections don't send packets while idle, so we use keep alives - // to avoid random hangs while we set up the connection again after - // inactivity. - node.KeepAlive = ok && peerStatus.Active + // Since we don't send nodes into Tailscale unless we're sure that the + // peer is ready for handshakes, we always enable keepalives. + node.KeepAlive = true } switch { case !ok && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: @@ -396,23 +398,46 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat if ps, ok := status.Peer[node.Key]; ok { lastHandshake = ps.LastHandshake } - c.peers[id] = &peerLifecycle{ - peerID: id, - node: node, - lastHandshake: lastHandshake, - lost: false, + lc = &peerLifecycle{ + peerID: id, + node: node, + lastHandshake: lastHandshake, + lost: false, + readyForHandshake: !c.waitForHandshake, } + if c.waitForHandshake { + lc.readyForHandshakeTimer = c.clock.AfterFunc(5*time.Second, func() { + logger.Debug(context.Background(), "ready for handshake timeout") + c.peerReadyForHandshakeTimeout(id) + }) + } + c.peers[id] = lc logger.Debug(context.Background(), "adding new peer") - return true + // since we just got this node, we don't know if it's ready for + // handshakes yet. + return lc.readyForHandshake case ok && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: // update node.Created = lc.node.Created - dirty = !lc.node.Equal(node) + dirty = !lc.node.Equal(node) && lc.readyForHandshake lc.node = node lc.lost = false lc.resetTimer() logger.Debug(context.Background(), "node update to existing peer", slog.F("dirty", dirty)) return dirty + case ok && update.Kind == proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE: + wasReady := lc.readyForHandshake + lc.readyForHandshake = true + if lc.readyForHandshakeTimer != nil { + lc.readyForHandshakeTimer.Stop() + } + logger.Debug(context.Background(), "peer ready for handshake") + return !wasReady + case !ok && update.Kind == proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE: + // TODO: should we keep track of ready for handshake messages we get + // from unknown nodes? + logger.Debug(context.Background(), "got peer ready for handshake for unknown peer") + return false case !ok: // disconnected or lost, but we don't have the node. No op logger.Debug(context.Background(), "skipping update for peer we don't recognize") @@ -550,12 +575,31 @@ func (c *configMaps) fillPeerDiagnostics(d *PeerDiagnostics, peerID uuid.UUID) { d.LastWireguardHandshake = ps.LastHandshake } +func (c *configMaps) peerReadyForHandshakeTimeout(peerID uuid.UUID) { + c.L.Lock() + defer c.L.Unlock() + lc, ok := c.peers[peerID] + if !ok { + return + } + if lc.readyForHandshakeTimer != nil { + wasReady := lc.readyForHandshake + lc.readyForHandshakeTimer = nil + lc.readyForHandshake = true + if !wasReady { + c.Broadcast() + } + } +} + type peerLifecycle struct { - peerID uuid.UUID - node *tailcfg.Node - lost bool - lastHandshake time.Time - timer *clock.Timer + peerID uuid.UUID + node *tailcfg.Node + lost bool + lastHandshake time.Time + timer *clock.Timer + readyForHandshake bool + readyForHandshakeTimer *clock.Timer } func (l *peerLifecycle) resetTimer() { diff --git a/tailnet/configmaps_internal_test.go b/tailnet/configmaps_internal_test.go index 1008562904b72..b1960088774dc 100644 --- a/tailnet/configmaps_internal_test.go +++ b/tailnet/configmaps_internal_test.go @@ -185,6 +185,75 @@ func TestConfigMaps_updatePeers_new(t *testing.T) { _ = testutil.RequireRecvCtx(ctx, t, done) } +func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + fEng := newFakeEngineConfigurable() + nodePrivateKey := key.NewNode() + nodeID := tailcfg.NodeID(5) + discoKey := key.NewDisco() + uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public()) + defer uut.close() + uut.setWaitForHandshake(true) + + p1ID := uuid.UUID{1} + p1Node := newTestNode(1) + p1n, err := NodeToProto(p1Node) + require.NoError(t, err) + + go func() { + <-fEng.status + fEng.statusDone <- struct{}{} + }() + + u1 := []*proto.CoordinateResponse_PeerUpdate{ + { + Id: p1ID[:], + Kind: proto.CoordinateResponse_PeerUpdate_NODE, + Node: p1n, + }, + } + uut.updatePeers(u1) + + // it should not send the peer to the netmap yet + + go func() { + <-fEng.status + fEng.statusDone <- struct{}{} + }() + + u2 := []*proto.CoordinateResponse_PeerUpdate{ + { + Id: p1ID[:], + Kind: proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE, + }, + } + uut.updatePeers(u2) + + // it should now send the peer to the netmap + + nm := testutil.RequireRecvCtx(ctx, t, fEng.setNetworkMap) + r := testutil.RequireRecvCtx(ctx, t, fEng.reconfig) + + require.Len(t, nm.Peers, 1) + n1 := getNodeWithID(t, nm.Peers, 1) + require.Equal(t, "127.3.3.40:1", n1.DERP) + require.Equal(t, p1Node.Endpoints, n1.Endpoints) + require.True(t, n1.KeepAlive) + + // we rely on nmcfg.WGCfg() to convert the netmap to wireguard config, so just + // require the right number of peers. + require.Len(t, r.wg.Peers, 1) + + done := make(chan struct{}) + go func() { + defer close(done) + uut.close() + }() + _ = testutil.RequireRecvCtx(ctx, t, done) +} + func TestConfigMaps_updatePeers_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) diff --git a/tailnet/conn.go b/tailnet/conn.go index e6dbdfdc3843a..e372d4fce6c8c 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -87,8 +87,8 @@ type Options struct { // connections, rather than trying `Upgrade: derp` first and potentially // falling back. This is useful for misbehaving proxies that prevent // fallback due to odd behavior, like Azure App Proxy. - DERPForceWebSockets bool - + DERPForceWebSockets bool + ShouldWaitForHandshake bool // BlockEndpoints specifies whether P2P endpoints are blocked. // If so, only DERPs can establish connections. BlockEndpoints bool @@ -216,6 +216,7 @@ func NewConn(options *Options) (conn *Conn, err error) { nodePrivateKey, magicConn.DiscoPublicKey(), ) + cfgMaps.setWaitForHandshake(options.ShouldWaitForHandshake) cfgMaps.setAddresses(options.Addresses) if options.DERPMap != nil { cfgMaps.setDERPMap(options.DERPMap) diff --git a/tailnet/coordinator.go b/tailnet/coordinator.go index f5025177bc255..ae6f8a1caad3e 100644 --- a/tailnet/coordinator.go +++ b/tailnet/coordinator.go @@ -102,7 +102,6 @@ type Coordinatee interface { } type Coordination interface { - AwaitAck() <-chan struct{} io.Closer Error() <-chan error } @@ -116,10 +115,6 @@ type remoteCoordination struct { logger slog.Logger protocol proto.DRPCTailnet_CoordinateClient respLoopDone chan struct{} - - ackOnce sync.Once - // tgtAck is closed when an ack from tgt is received. - tgtAck chan struct{} } func (c *remoteCoordination) Close() (retErr error) { @@ -168,53 +163,39 @@ func (c *remoteCoordination) respLoop() { return } - if len(resp.GetPeerUpdates()) > 0 { - err = c.coordinatee.UpdatePeers(resp.GetPeerUpdates()) - if err != nil { - c.sendErr(xerrors.Errorf("update peers: %w", err)) - return - } + err = c.coordinatee.UpdatePeers(resp.GetPeerUpdates()) + if err != nil { + c.sendErr(xerrors.Errorf("update peers: %w", err)) + return + } - // Only send acks from agents. - if c.tgt == uuid.Nil { - // Send an ack back for all received peers. This could - // potentially be smarter to only send an ACK once per client, - // but there's nothing currently stopping clients from reusing - // IDs. - for _, peer := range resp.GetPeerUpdates() { - err := c.protocol.Send(&proto.CoordinateRequest{ - TunnelAck: &proto.CoordinateRequest_Ack{Id: peer.Id}, - }) - if err != nil { - c.sendErr(xerrors.Errorf("send: %w", err)) - return - } + // Only send acks from peers without a target. + if c.tgt == uuid.Nil { + // Send an ack back for all received peers. This could + // potentially be smarter to only send an ACK once per client, + // but there's nothing currently stopping clients from reusing + // IDs. + rfh := []*proto.CoordinateRequest_ReadyForHandshake{} + for _, peer := range resp.GetPeerUpdates() { + if peer.Kind != proto.CoordinateResponse_PeerUpdate_NODE { + continue } - } - } - // If we receive an ack, close the tgtAck channel to notify the waiting - // client. - if ack := resp.GetTunnelAck(); ack != nil { - dstID, err := uuid.FromBytes(ack.Id) - if err != nil { - c.sendErr(xerrors.Errorf("parse ack id: %w", err)) - return + rfh = append(rfh, &proto.CoordinateRequest_ReadyForHandshake{Id: peer.Id}) } - - if c.tgt == dstID { - c.ackOnce.Do(func() { - close(c.tgtAck) + if len(rfh) > 0 { + err := c.protocol.Send(&proto.CoordinateRequest{ + ReadyForHandshake: rfh, }) + if err != nil { + c.sendErr(xerrors.Errorf("send: %w", err)) + return + } } } } } -func (c *remoteCoordination) AwaitAck() <-chan struct{} { - return c.tgtAck -} - // NewRemoteCoordination uses the provided protocol to coordinate the provided coordinatee (usually a // Conn). If the tunnelTarget is not uuid.Nil, then we add a tunnel to the peer (i.e. we are acting as // a client---agents should NOT set this!). @@ -229,7 +210,6 @@ func NewRemoteCoordination(logger slog.Logger, logger: logger, protocol: protocol, respLoopDone: make(chan struct{}), - tgtAck: make(chan struct{}), } if tunnelTarget != uuid.Nil { c.Lock() @@ -713,8 +693,8 @@ func (c *core) handleRequest(p *peer, req *proto.CoordinateRequest) error { if req.Disconnect != nil { c.removePeerLocked(p.id, proto.CoordinateResponse_PeerUpdate_DISCONNECTED, "graceful disconnect") } - if ack := req.TunnelAck; ack != nil { - err := c.handleAckLocked(pr, ack) + if rfhs := req.ReadyForHandshake; rfhs != nil { + err := c.handleReadyForHandshakeLocked(pr, rfhs) if err != nil { return xerrors.Errorf("handle ack: %w", err) } @@ -722,20 +702,27 @@ func (c *core) handleRequest(p *peer, req *proto.CoordinateRequest) error { return nil } -func (c *core) handleAckLocked(src *peer, ack *proto.CoordinateRequest_Ack) error { - dstID, err := uuid.FromBytes(ack.Id) - if err != nil { - // this shouldn't happen unless there is a client error. Close the connection so the client - // doesn't just happily continue thinking everything is fine. - return xerrors.Errorf("unable to convert bytes to UUID: %w", err) - } +func (c *core) handleReadyForHandshakeLocked(src *peer, rfhs []*proto.CoordinateRequest_ReadyForHandshake) error { + for _, rfh := range rfhs { + dstID, err := uuid.FromBytes(rfh.Id) + if err != nil { + // this shouldn't happen unless there is a client error. Close the connection so the client + // doesn't just happily continue thinking everything is fine. + return xerrors.Errorf("unable to convert bytes to UUID: %w", err) + } - dst, ok := c.peers[dstID] - if ok { - dst.resps <- &proto.CoordinateResponse{ - TunnelAck: &proto.CoordinateResponse_Ack{ - Id: src.id[:], - }, + dst, ok := c.peers[dstID] + if ok { + select { + case dst.resps <- &proto.CoordinateResponse{ + PeerUpdates: []*proto.CoordinateResponse_PeerUpdate{{ + Id: src.id[:], + Kind: proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE, + }}, + }: + default: + return ErrWouldBlock + } } } return nil diff --git a/tailnet/coordinator_test.go b/tailnet/coordinator_test.go index ca89b03180616..a6144ba9c9127 100644 --- a/tailnet/coordinator_test.go +++ b/tailnet/coordinator_test.go @@ -425,10 +425,14 @@ func TestCoordinator(t *testing.T) { aReq, _ := coordinator.Coordinate(ctx, agentID, agentID.String(), tailnet.AgentCoordinateeAuth{ID: agentID}) _, cRes := coordinator.Coordinate(ctx, clientID, clientID.String(), tailnet.ClientCoordinateeAuth{AgentID: agentID}) - aReq <- &proto.CoordinateRequest{TunnelAck: &proto.CoordinateRequest_Ack{Id: clientID[:]}} + aReq <- &proto.CoordinateRequest{ReadyForHandshake: []*proto.CoordinateRequest_ReadyForHandshake{{ + Id: clientID[:], + }}} ack := testutil.RequireRecvCtx(ctx, t, cRes) - require.NotNil(t, ack.TunnelAck) - require.Equal(t, agentID[:], ack.TunnelAck.Id) + require.NotNil(t, ack.PeerUpdates) + require.Len(t, ack.PeerUpdates, 1) + require.Equal(t, proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE, ack.PeerUpdates[0].Kind) + require.Equal(t, agentID[:], ack.PeerUpdates[0].Id) }) } @@ -656,7 +660,7 @@ func TestRemoteCoordination(t *testing.T) { } } -func TestRemoteCoordination_Ack(t *testing.T) { +func TestRemoteCoordination_SendsReadyForHandshake(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) @@ -692,14 +696,29 @@ func TestRemoteCoordination_Ack(t *testing.T) { protocol, err := client.Coordinate(ctx) require.NoError(t, err) - uut := tailnet.NewRemoteCoordination(logger.Named("coordination"), protocol, fConn, agentID) + uut := tailnet.NewRemoteCoordination(logger.Named("coordination"), protocol, fConn, uuid.UUID{}) defer uut.Close() + nk, err := key.NewNode().Public().MarshalBinary() + require.NoError(t, err) + dk, err := key.NewDisco().Public().MarshalText() + require.NoError(t, err) testutil.RequireSendCtx(ctx, t, resps, &proto.CoordinateResponse{ - TunnelAck: &proto.CoordinateResponse_Ack{Id: agentID[:]}, + PeerUpdates: []*proto.CoordinateResponse_PeerUpdate{{ + Id: clientID[:], + Kind: proto.CoordinateResponse_PeerUpdate_NODE, + Node: &proto.Node{ + Id: 3, + Key: nk, + Disco: string(dk), + }, + }}, }) - testutil.RequireRecvCtx(ctx, t, uut.AwaitAck()) + rfh := testutil.RequireRecvCtx(ctx, t, reqs) + require.NotNil(t, rfh.ReadyForHandshake) + require.Len(t, rfh.ReadyForHandshake, 1) + require.Equal(t, clientID[:], rfh.ReadyForHandshake[0].Id) require.NoError(t, uut.Close()) diff --git a/tailnet/proto/tailnet.pb.go b/tailnet/proto/tailnet.pb.go index 6b1b09d6495e3..6e9368602bd92 100644 --- a/tailnet/proto/tailnet.pb.go +++ b/tailnet/proto/tailnet.pb.go @@ -24,10 +24,11 @@ const ( type CoordinateResponse_PeerUpdate_Kind int32 const ( - CoordinateResponse_PeerUpdate_KIND_UNSPECIFIED CoordinateResponse_PeerUpdate_Kind = 0 - CoordinateResponse_PeerUpdate_NODE CoordinateResponse_PeerUpdate_Kind = 1 - CoordinateResponse_PeerUpdate_DISCONNECTED CoordinateResponse_PeerUpdate_Kind = 2 - CoordinateResponse_PeerUpdate_LOST CoordinateResponse_PeerUpdate_Kind = 3 + CoordinateResponse_PeerUpdate_KIND_UNSPECIFIED CoordinateResponse_PeerUpdate_Kind = 0 + CoordinateResponse_PeerUpdate_NODE CoordinateResponse_PeerUpdate_Kind = 1 + CoordinateResponse_PeerUpdate_DISCONNECTED CoordinateResponse_PeerUpdate_Kind = 2 + CoordinateResponse_PeerUpdate_LOST CoordinateResponse_PeerUpdate_Kind = 3 + CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE CoordinateResponse_PeerUpdate_Kind = 4 ) // Enum value maps for CoordinateResponse_PeerUpdate_Kind. @@ -37,12 +38,14 @@ var ( 1: "NODE", 2: "DISCONNECTED", 3: "LOST", + 4: "READY_FOR_HANDSHAKE", } CoordinateResponse_PeerUpdate_Kind_value = map[string]int32{ - "KIND_UNSPECIFIED": 0, - "NODE": 1, - "DISCONNECTED": 2, - "LOST": 3, + "KIND_UNSPECIFIED": 0, + "NODE": 1, + "DISCONNECTED": 2, + "LOST": 3, + "READY_FOR_HANDSHAKE": 4, } ) @@ -291,11 +294,11 @@ type CoordinateRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - UpdateSelf *CoordinateRequest_UpdateSelf `protobuf:"bytes,1,opt,name=update_self,json=updateSelf,proto3" json:"update_self,omitempty"` - Disconnect *CoordinateRequest_Disconnect `protobuf:"bytes,2,opt,name=disconnect,proto3" json:"disconnect,omitempty"` - AddTunnel *CoordinateRequest_Tunnel `protobuf:"bytes,3,opt,name=add_tunnel,json=addTunnel,proto3" json:"add_tunnel,omitempty"` - RemoveTunnel *CoordinateRequest_Tunnel `protobuf:"bytes,4,opt,name=remove_tunnel,json=removeTunnel,proto3" json:"remove_tunnel,omitempty"` - TunnelAck *CoordinateRequest_Ack `protobuf:"bytes,5,opt,name=tunnel_ack,json=tunnelAck,proto3" json:"tunnel_ack,omitempty"` + UpdateSelf *CoordinateRequest_UpdateSelf `protobuf:"bytes,1,opt,name=update_self,json=updateSelf,proto3" json:"update_self,omitempty"` + Disconnect *CoordinateRequest_Disconnect `protobuf:"bytes,2,opt,name=disconnect,proto3" json:"disconnect,omitempty"` + AddTunnel *CoordinateRequest_Tunnel `protobuf:"bytes,3,opt,name=add_tunnel,json=addTunnel,proto3" json:"add_tunnel,omitempty"` + RemoveTunnel *CoordinateRequest_Tunnel `protobuf:"bytes,4,opt,name=remove_tunnel,json=removeTunnel,proto3" json:"remove_tunnel,omitempty"` + ReadyForHandshake []*CoordinateRequest_ReadyForHandshake `protobuf:"bytes,5,rep,name=ready_for_handshake,json=readyForHandshake,proto3" json:"ready_for_handshake,omitempty"` } func (x *CoordinateRequest) Reset() { @@ -358,9 +361,9 @@ func (x *CoordinateRequest) GetRemoveTunnel() *CoordinateRequest_Tunnel { return nil } -func (x *CoordinateRequest) GetTunnelAck() *CoordinateRequest_Ack { +func (x *CoordinateRequest) GetReadyForHandshake() []*CoordinateRequest_ReadyForHandshake { if x != nil { - return x.TunnelAck + return x.ReadyForHandshake } return nil } @@ -371,7 +374,6 @@ type CoordinateResponse struct { unknownFields protoimpl.UnknownFields PeerUpdates []*CoordinateResponse_PeerUpdate `protobuf:"bytes,1,rep,name=peer_updates,json=peerUpdates,proto3" json:"peer_updates,omitempty"` - TunnelAck *CoordinateResponse_Ack `protobuf:"bytes,2,opt,name=tunnel_ack,json=tunnelAck,proto3" json:"tunnel_ack,omitempty"` } func (x *CoordinateResponse) Reset() { @@ -413,13 +415,6 @@ func (x *CoordinateResponse) GetPeerUpdates() []*CoordinateResponse_PeerUpdate { return nil } -func (x *CoordinateResponse) GetTunnelAck() *CoordinateResponse_Ack { - if x != nil { - return x.TunnelAck - } - return nil -} - type DERPMap_HomeParams struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -829,10 +824,11 @@ func (x *CoordinateRequest_Tunnel) GetId() []byte { return nil } -// Acks are sent from agents back to the client, acknowledging receipt of -// the client's node. If the client starts pinging before an ACK, the -// handshake will likely be dropped. -type CoordinateRequest_Ack struct { +// ReadyForHandskales are sent from destinations back to the source, +// acknowledging receipt of the source's node. If the source starts pinging +// before a ReadyForHandshake, the Wireguard handshake will likely be +// dropped. +type CoordinateRequest_ReadyForHandshake struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -840,8 +836,8 @@ type CoordinateRequest_Ack struct { Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } -func (x *CoordinateRequest_Ack) Reset() { - *x = CoordinateRequest_Ack{} +func (x *CoordinateRequest_ReadyForHandshake) Reset() { + *x = CoordinateRequest_ReadyForHandshake{} if protoimpl.UnsafeEnabled { mi := &file_tailnet_proto_tailnet_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -849,13 +845,13 @@ func (x *CoordinateRequest_Ack) Reset() { } } -func (x *CoordinateRequest_Ack) String() string { +func (x *CoordinateRequest_ReadyForHandshake) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CoordinateRequest_Ack) ProtoMessage() {} +func (*CoordinateRequest_ReadyForHandshake) ProtoMessage() {} -func (x *CoordinateRequest_Ack) ProtoReflect() protoreflect.Message { +func (x *CoordinateRequest_ReadyForHandshake) ProtoReflect() protoreflect.Message { mi := &file_tailnet_proto_tailnet_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -867,12 +863,12 @@ func (x *CoordinateRequest_Ack) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CoordinateRequest_Ack.ProtoReflect.Descriptor instead. -func (*CoordinateRequest_Ack) Descriptor() ([]byte, []int) { +// Deprecated: Use CoordinateRequest_ReadyForHandshake.ProtoReflect.Descriptor instead. +func (*CoordinateRequest_ReadyForHandshake) Descriptor() ([]byte, []int) { return file_tailnet_proto_tailnet_proto_rawDescGZIP(), []int{3, 3} } -func (x *CoordinateRequest_Ack) GetId() []byte { +func (x *CoordinateRequest_ReadyForHandshake) GetId() []byte { if x != nil { return x.Id } @@ -950,53 +946,6 @@ func (x *CoordinateResponse_PeerUpdate) GetReason() string { return "" } -type CoordinateResponse_Ack struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *CoordinateResponse_Ack) Reset() { - *x = CoordinateResponse_Ack{} - if protoimpl.UnsafeEnabled { - mi := &file_tailnet_proto_tailnet_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CoordinateResponse_Ack) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CoordinateResponse_Ack) ProtoMessage() {} - -func (x *CoordinateResponse_Ack) ProtoReflect() protoreflect.Message { - mi := &file_tailnet_proto_tailnet_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CoordinateResponse_Ack.ProtoReflect.Descriptor instead. -func (*CoordinateResponse_Ack) Descriptor() ([]byte, []int) { - return file_tailnet_proto_tailnet_proto_rawDescGZIP(), []int{4, 1} -} - -func (x *CoordinateResponse_Ack) GetId() []byte { - if x != nil { - return x.Id - } - return nil -} - var File_tailnet_proto_tailnet_proto protoreflect.FileDescriptor var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ @@ -1105,7 +1054,7 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x91, 0x04, 0x0a, 0x11, 0x43, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xbe, 0x04, 0x0a, 0x11, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, @@ -1126,62 +1075,61 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x01, 0x28, 0x0b, 0x32, 0x2a, 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, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, - 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x46, 0x0a, - 0x0a, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x61, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x63, 0x6b, 0x52, 0x09, 0x74, 0x75, 0x6e, 0x6e, - 0x65, 0x6c, 0x41, 0x63, 0x6b, 0x1a, 0x38, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, - 0x65, 0x6c, 0x66, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x1a, - 0x0c, 0x0a, 0x0a, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x1a, 0x18, 0x0a, - 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x1a, 0x15, 0x0a, 0x03, 0x41, 0x63, 0x6b, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, 0xb9, - 0x03, 0x0a, 0x12, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 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, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x70, 0x65, - 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x0a, 0x74, 0x75, 0x6e, - 0x6e, 0x65, 0x6c, 0x5f, 0x61, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 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, 0x2e, 0x41, 0x63, 0x6b, 0x52, 0x09, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x41, - 0x63, 0x6b, 0x1a, 0xee, 0x01, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x48, 0x0a, - 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x34, 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, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x4b, 0x69, 0x6e, - 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, - 0x42, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x10, 0x4b, 0x49, 0x4e, 0x44, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, - 0x04, 0x4e, 0x4f, 0x44, 0x45, 0x10, 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, 0x1a, 0x15, 0x0a, 0x03, 0x41, 0x63, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x32, 0xbe, 0x01, 0x0a, 0x07, 0x54, - 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 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, + 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x65, 0x0a, + 0x13, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x73, + 0x68, 0x61, 0x6b, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 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, 0x2e, + 0x52, 0x65, 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, + 0x65, 0x52, 0x11, 0x72, 0x65, 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72, 0x48, 0x61, 0x6e, 0x64, 0x73, + 0x68, 0x61, 0x6b, 0x65, 0x1a, 0x38, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, + 0x6c, 0x66, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x1a, 0x0c, + 0x0a, 0x0a, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x1a, 0x18, 0x0a, 0x06, + 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x1a, 0x23, 0x0a, 0x11, 0x52, 0x65, 0x61, 0x64, 0x79, 0x46, + 0x6f, 0x72, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, 0xf2, 0x02, 0x0a, 0x12, + 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 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, 0x2e, 0x50, + 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x1a, 0x87, 0x02, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, + 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, + 0x65, 0x12, 0x48, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x34, 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, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x22, 0x5b, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x10, 0x4b, + 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x44, 0x45, 0x10, 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, + 0x32, 0xbe, 0x01, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x12, 0x56, 0x0a, 0x0e, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x27, + 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, + 0x61, 0x70, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, + 0x74, 0x65, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, + 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, + 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, + 0x01, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, + 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1197,7 +1145,7 @@ func file_tailnet_proto_tailnet_proto_rawDescGZIP() []byte { } var file_tailnet_proto_tailnet_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_tailnet_proto_tailnet_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_tailnet_proto_tailnet_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_tailnet_proto_tailnet_proto_goTypes = []interface{}{ (CoordinateResponse_PeerUpdate_Kind)(0), // 0: coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind (*DERPMap)(nil), // 1: coder.tailnet.v2.DERPMap @@ -1215,39 +1163,37 @@ var file_tailnet_proto_tailnet_proto_goTypes = []interface{}{ (*CoordinateRequest_UpdateSelf)(nil), // 13: coder.tailnet.v2.CoordinateRequest.UpdateSelf (*CoordinateRequest_Disconnect)(nil), // 14: coder.tailnet.v2.CoordinateRequest.Disconnect (*CoordinateRequest_Tunnel)(nil), // 15: coder.tailnet.v2.CoordinateRequest.Tunnel - (*CoordinateRequest_Ack)(nil), // 16: coder.tailnet.v2.CoordinateRequest.Ack - (*CoordinateResponse_PeerUpdate)(nil), // 17: coder.tailnet.v2.CoordinateResponse.PeerUpdate - (*CoordinateResponse_Ack)(nil), // 18: coder.tailnet.v2.CoordinateResponse.Ack - (*timestamppb.Timestamp)(nil), // 19: google.protobuf.Timestamp + (*CoordinateRequest_ReadyForHandshake)(nil), // 16: coder.tailnet.v2.CoordinateRequest.ReadyForHandshake + (*CoordinateResponse_PeerUpdate)(nil), // 17: coder.tailnet.v2.CoordinateResponse.PeerUpdate + (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp } var file_tailnet_proto_tailnet_proto_depIdxs = []int32{ 6, // 0: coder.tailnet.v2.DERPMap.home_params:type_name -> coder.tailnet.v2.DERPMap.HomeParams 8, // 1: coder.tailnet.v2.DERPMap.regions:type_name -> coder.tailnet.v2.DERPMap.RegionsEntry - 19, // 2: coder.tailnet.v2.Node.as_of:type_name -> google.protobuf.Timestamp + 18, // 2: coder.tailnet.v2.Node.as_of:type_name -> google.protobuf.Timestamp 11, // 3: coder.tailnet.v2.Node.derp_latency:type_name -> coder.tailnet.v2.Node.DerpLatencyEntry 12, // 4: coder.tailnet.v2.Node.derp_forced_websocket:type_name -> coder.tailnet.v2.Node.DerpForcedWebsocketEntry 13, // 5: coder.tailnet.v2.CoordinateRequest.update_self:type_name -> coder.tailnet.v2.CoordinateRequest.UpdateSelf 14, // 6: coder.tailnet.v2.CoordinateRequest.disconnect:type_name -> coder.tailnet.v2.CoordinateRequest.Disconnect 15, // 7: coder.tailnet.v2.CoordinateRequest.add_tunnel:type_name -> coder.tailnet.v2.CoordinateRequest.Tunnel 15, // 8: coder.tailnet.v2.CoordinateRequest.remove_tunnel:type_name -> coder.tailnet.v2.CoordinateRequest.Tunnel - 16, // 9: coder.tailnet.v2.CoordinateRequest.tunnel_ack:type_name -> coder.tailnet.v2.CoordinateRequest.Ack + 16, // 9: coder.tailnet.v2.CoordinateRequest.ready_for_handshake:type_name -> coder.tailnet.v2.CoordinateRequest.ReadyForHandshake 17, // 10: coder.tailnet.v2.CoordinateResponse.peer_updates:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate - 18, // 11: coder.tailnet.v2.CoordinateResponse.tunnel_ack:type_name -> coder.tailnet.v2.CoordinateResponse.Ack - 9, // 12: coder.tailnet.v2.DERPMap.HomeParams.region_score:type_name -> coder.tailnet.v2.DERPMap.HomeParams.RegionScoreEntry - 10, // 13: coder.tailnet.v2.DERPMap.Region.nodes:type_name -> coder.tailnet.v2.DERPMap.Region.Node - 7, // 14: coder.tailnet.v2.DERPMap.RegionsEntry.value:type_name -> coder.tailnet.v2.DERPMap.Region - 3, // 15: coder.tailnet.v2.CoordinateRequest.UpdateSelf.node:type_name -> coder.tailnet.v2.Node - 3, // 16: coder.tailnet.v2.CoordinateResponse.PeerUpdate.node:type_name -> coder.tailnet.v2.Node - 0, // 17: coder.tailnet.v2.CoordinateResponse.PeerUpdate.kind:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind - 2, // 18: coder.tailnet.v2.Tailnet.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest - 4, // 19: coder.tailnet.v2.Tailnet.Coordinate:input_type -> coder.tailnet.v2.CoordinateRequest - 1, // 20: coder.tailnet.v2.Tailnet.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap - 5, // 21: coder.tailnet.v2.Tailnet.Coordinate:output_type -> coder.tailnet.v2.CoordinateResponse - 20, // [20:22] is the sub-list for method output_type - 18, // [18:20] is the sub-list for method input_type - 18, // [18:18] is the sub-list for extension type_name - 18, // [18:18] is the sub-list for extension extendee - 0, // [0:18] is the sub-list for field type_name + 9, // 11: coder.tailnet.v2.DERPMap.HomeParams.region_score:type_name -> coder.tailnet.v2.DERPMap.HomeParams.RegionScoreEntry + 10, // 12: coder.tailnet.v2.DERPMap.Region.nodes:type_name -> coder.tailnet.v2.DERPMap.Region.Node + 7, // 13: coder.tailnet.v2.DERPMap.RegionsEntry.value:type_name -> coder.tailnet.v2.DERPMap.Region + 3, // 14: coder.tailnet.v2.CoordinateRequest.UpdateSelf.node:type_name -> coder.tailnet.v2.Node + 3, // 15: coder.tailnet.v2.CoordinateResponse.PeerUpdate.node:type_name -> coder.tailnet.v2.Node + 0, // 16: coder.tailnet.v2.CoordinateResponse.PeerUpdate.kind:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind + 2, // 17: coder.tailnet.v2.Tailnet.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest + 4, // 18: coder.tailnet.v2.Tailnet.Coordinate:input_type -> coder.tailnet.v2.CoordinateRequest + 1, // 19: coder.tailnet.v2.Tailnet.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap + 5, // 20: coder.tailnet.v2.Tailnet.Coordinate:output_type -> coder.tailnet.v2.CoordinateResponse + 19, // [19:21] is the sub-list for method output_type + 17, // [17:19] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name } func init() { file_tailnet_proto_tailnet_proto_init() } @@ -1389,7 +1335,7 @@ func file_tailnet_proto_tailnet_proto_init() { } } file_tailnet_proto_tailnet_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CoordinateRequest_Ack); i { + switch v := v.(*CoordinateRequest_ReadyForHandshake); i { case 0: return &v.state case 1: @@ -1412,18 +1358,6 @@ func file_tailnet_proto_tailnet_proto_init() { return nil } } - file_tailnet_proto_tailnet_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CoordinateResponse_Ack); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1431,7 +1365,7 @@ func file_tailnet_proto_tailnet_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_tailnet_proto_tailnet_proto_rawDesc, NumEnums: 1, - NumMessages: 18, + NumMessages: 17, NumExtensions: 0, NumServices: 1, }, diff --git a/tailnet/proto/tailnet.proto b/tailnet/proto/tailnet.proto index 887d49afdf6a9..54230a0ce0131 100644 --- a/tailnet/proto/tailnet.proto +++ b/tailnet/proto/tailnet.proto @@ -69,13 +69,14 @@ message CoordinateRequest { Tunnel add_tunnel = 3; Tunnel remove_tunnel = 4; - // Acks are sent from agents back to the client, acknowledging receipt of - // the client's node. If the client starts pinging before an ACK, the - // handshake will likely be dropped. - message Ack { + // ReadyForHandskales are sent from destinations back to the source, + // acknowledging receipt of the source's node. If the source starts pinging + // before a ReadyForHandshake, the Wireguard handshake will likely be + // dropped. + message ReadyForHandshake { bytes id = 1; } - Ack tunnel_ack = 5; + repeated ReadyForHandshake ready_for_handshake = 5; } message CoordinateResponse { @@ -88,17 +89,13 @@ message CoordinateResponse { NODE = 1; DISCONNECTED = 2; LOST = 3; + READY_FOR_HANDSHAKE = 4; } Kind kind = 3; string reason = 4; } repeated PeerUpdate peer_updates = 1; - - message Ack { - bytes id = 1; - } - Ack tunnel_ack = 2; } service Tailnet { From 7e2d1bb390c3f59bbdf47bafc281b4bd1e75ec36 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 29 Mar 2024 02:04:36 +0000 Subject: [PATCH 03/14] fixup! spike comments --- tailnet/configmaps.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tailnet/configmaps.go b/tailnet/configmaps.go index b6e69d3196f60..75e0f6b0cfa24 100644 --- a/tailnet/configmaps.go +++ b/tailnet/configmaps.go @@ -387,9 +387,18 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat return false } logger = logger.With(slog.F("key_id", node.Key.ShortString()), slog.F("node", node)) - // Since we don't send nodes into Tailscale unless we're sure that the - // peer is ready for handshakes, we always enable keepalives. - node.KeepAlive = true + peerStatus, ok := status.Peer[node.Key] + // Starting KeepAlive messages at the initialization of a connection + // causes a race condition. If we send the handshake before the peer has + // our node, we'll have to wait for 5 seconds before trying again. + // Ideally, the first handshake starts when the user first initiates a + // connection to the peer. After a successful connection we enable + // keep alives to persist the connection and keep it from becoming idle. + // SSH connections don't send packets while idle, so we use keep alives + // to avoid random hangs while we set up the connection again after + // inactivity. + // TODO: tie this into waitForHandshake (upstack PR) + node.KeepAlive = ok && peerStatus.Active } switch { case !ok && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: From 3557c11131e3e891a3265808bbbc2650c1b07009 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 29 Mar 2024 20:44:09 +0000 Subject: [PATCH 04/14] additional tests --- tailnet/configmaps.go | 24 +++--- tailnet/configmaps_internal_test.go | 110 ++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 10 deletions(-) diff --git a/tailnet/configmaps.go b/tailnet/configmaps.go index 75e0f6b0cfa24..3f0d0c8410310 100644 --- a/tailnet/configmaps.go +++ b/tailnet/configmaps.go @@ -217,7 +217,9 @@ func (c *configMaps) netMapLocked() *netmap.NetworkMap { func (c *configMaps) peerConfigLocked() []*tailcfg.Node { out := make([]*tailcfg.Node, 0, len(c.peers)) for _, p := range c.peers { - if !p.readyForHandshake { + // Don't add nodes that we havent received a READY_FOR_HANDSHAKE for + // yet, if we want to wait for them. + if !p.readyForHandshake && c.waitForHandshake { continue } n := p.node.Clone() @@ -373,7 +375,7 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat return false } logger := c.logger.With(slog.F("peer_id", id)) - lc, ok := c.peers[id] + lc, peerOk := c.peers[id] var node *tailcfg.Node if update.Kind == proto.CoordinateResponse_PeerUpdate_NODE { // If no preferred DERP is provided, we can't reach the node. @@ -387,7 +389,7 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat return false } logger = logger.With(slog.F("key_id", node.Key.ShortString()), slog.F("node", node)) - peerStatus, ok := status.Peer[node.Key] + peerStatus, statusOk := status.Peer[node.Key] // Starting KeepAlive messages at the initialization of a connection // causes a race condition. If we send the handshake before the peer has // our node, we'll have to wait for 5 seconds before trying again. @@ -397,11 +399,10 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat // SSH connections don't send packets while idle, so we use keep alives // to avoid random hangs while we set up the connection again after // inactivity. - // TODO: tie this into waitForHandshake (upstack PR) - node.KeepAlive = ok && peerStatus.Active + node.KeepAlive = (statusOk && peerStatus.Active) || (peerOk && lc.node.KeepAlive) } switch { - case !ok && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: + case !peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: // new! var lastHandshake time.Time if ps, ok := status.Peer[node.Key]; ok { @@ -425,7 +426,7 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat // since we just got this node, we don't know if it's ready for // handshakes yet. return lc.readyForHandshake - case ok && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: + case peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: // update node.Created = lc.node.Created dirty = !lc.node.Equal(node) && lc.readyForHandshake @@ -434,20 +435,22 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat lc.resetTimer() logger.Debug(context.Background(), "node update to existing peer", slog.F("dirty", dirty)) return dirty - case ok && update.Kind == proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE: + case peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE: wasReady := lc.readyForHandshake lc.readyForHandshake = true if lc.readyForHandshakeTimer != nil { lc.readyForHandshakeTimer.Stop() + lc.readyForHandshakeTimer = nil } + lc.node.KeepAlive = true logger.Debug(context.Background(), "peer ready for handshake") return !wasReady - case !ok && update.Kind == proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE: + case !peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE: // TODO: should we keep track of ready for handshake messages we get // from unknown nodes? logger.Debug(context.Background(), "got peer ready for handshake for unknown peer") return false - case !ok: + case !peerOk: // disconnected or lost, but we don't have the node. No op logger.Debug(context.Background(), "skipping update for peer we don't recognize") return false @@ -596,6 +599,7 @@ func (c *configMaps) peerReadyForHandshakeTimeout(peerID uuid.UUID) { lc.readyForHandshakeTimer = nil lc.readyForHandshake = true if !wasReady { + c.netmapDirty = true c.Broadcast() } } diff --git a/tailnet/configmaps_internal_test.go b/tailnet/configmaps_internal_test.go index b1960088774dc..05242b8399f27 100644 --- a/tailnet/configmaps_internal_test.go +++ b/tailnet/configmaps_internal_test.go @@ -185,6 +185,52 @@ func TestConfigMaps_updatePeers_new(t *testing.T) { _ = testutil.RequireRecvCtx(ctx, t, done) } +func TestConfigMaps_updatePeers_new_waitForHandshake_neverConfigures(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + fEng := newFakeEngineConfigurable() + nodePrivateKey := key.NewNode() + nodeID := tailcfg.NodeID(5) + discoKey := key.NewDisco() + uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public()) + defer uut.close() + uut.setWaitForHandshake(true) + start := time.Date(2024, time.January, 1, 8, 0, 0, 0, time.UTC) + mClock := clock.NewMock() + mClock.Set(start) + uut.clock = mClock + + p1ID := uuid.UUID{1} + p1Node := newTestNode(1) + p1n, err := NodeToProto(p1Node) + require.NoError(t, err) + + // it should not send the peer to the netmap + requireNeverConfigures(ctx, t, &uut.phased) + + go func() { + <-fEng.status + fEng.statusDone <- struct{}{} + }() + + u1 := []*proto.CoordinateResponse_PeerUpdate{ + { + Id: p1ID[:], + Kind: proto.CoordinateResponse_PeerUpdate_NODE, + Node: p1n, + }, + } + uut.updatePeers(u1) + + done := make(chan struct{}) + go func() { + defer close(done) + uut.close() + }() + _ = testutil.RequireRecvCtx(ctx, t, done) +} + func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) @@ -196,6 +242,10 @@ func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) { uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public()) defer uut.close() uut.setWaitForHandshake(true) + start := time.Date(2024, time.January, 1, 8, 0, 0, 0, time.UTC) + mClock := clock.NewMock() + mClock.Set(start) + uut.clock = mClock p1ID := uuid.UUID{1} p1Node := newTestNode(1) @@ -254,6 +304,66 @@ func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) { _ = testutil.RequireRecvCtx(ctx, t, done) } +func TestConfigMaps_updatePeers_new_waitForHandshake_timeout(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + fEng := newFakeEngineConfigurable() + nodePrivateKey := key.NewNode() + nodeID := tailcfg.NodeID(5) + discoKey := key.NewDisco() + uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public()) + defer uut.close() + uut.setWaitForHandshake(true) + start := time.Date(2024, time.March, 29, 8, 0, 0, 0, time.UTC) + mClock := clock.NewMock() + mClock.Set(start) + uut.clock = mClock + + p1ID := uuid.UUID{1} + p1Node := newTestNode(1) + p1n, err := NodeToProto(p1Node) + require.NoError(t, err) + + go func() { + <-fEng.status + fEng.statusDone <- struct{}{} + }() + + u1 := []*proto.CoordinateResponse_PeerUpdate{ + { + Id: p1ID[:], + Kind: proto.CoordinateResponse_PeerUpdate_NODE, + Node: p1n, + }, + } + uut.updatePeers(u1) + + mClock.Add(5 * time.Second) + + // it should now send the peer to the netmap + + nm := testutil.RequireRecvCtx(ctx, t, fEng.setNetworkMap) + r := testutil.RequireRecvCtx(ctx, t, fEng.reconfig) + + require.Len(t, nm.Peers, 1) + n1 := getNodeWithID(t, nm.Peers, 1) + require.Equal(t, "127.3.3.40:1", n1.DERP) + require.Equal(t, p1Node.Endpoints, n1.Endpoints) + require.False(t, n1.KeepAlive) + + // we rely on nmcfg.WGCfg() to convert the netmap to wireguard config, so just + // require the right number of peers. + require.Len(t, r.wg.Peers, 1) + + done := make(chan struct{}) + go func() { + defer close(done) + uut.close() + }() + _ = testutil.RequireRecvCtx(ctx, t, done) +} + func TestConfigMaps_updatePeers_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) From 134737034f98c6823336e457b7e1a6de6560d824 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 29 Mar 2024 20:54:38 +0000 Subject: [PATCH 05/14] prevent clients from sending `ready_for_handshake` --- tailnet/tunnel.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tailnet/tunnel.go b/tailnet/tunnel.go index bc5becbc94c26..faf7b2ec23906 100644 --- a/tailnet/tunnel.go +++ b/tailnet/tunnel.go @@ -52,6 +52,10 @@ func (c ClientCoordinateeAuth) Authorize(req *proto.CoordinateRequest) error { } } + if rfh := req.GetReadyForHandshake(); rfh != nil { + return xerrors.Errorf("clients may not send ready_for_handshake") + } + return nil } From 2896d6bffa561839f7f4fb9ba233774709d1e728 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 29 Mar 2024 20:56:32 +0000 Subject: [PATCH 06/14] rename --- tailnet/configmaps.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tailnet/configmaps.go b/tailnet/configmaps.go index 3f0d0c8410310..9a21d184d4ceb 100644 --- a/tailnet/configmaps.go +++ b/tailnet/configmaps.go @@ -56,11 +56,11 @@ type phased struct { type configMaps struct { phased - netmapDirty bool - derpMapDirty bool - filterDirty bool - closing bool - waitForHandshake bool + netmapDirty bool + derpMapDirty bool + filterDirty bool + closing bool + waitReadyForHandshake bool engine engineConfigurable static netmap.NetworkMap @@ -219,7 +219,7 @@ func (c *configMaps) peerConfigLocked() []*tailcfg.Node { for _, p := range c.peers { // Don't add nodes that we havent received a READY_FOR_HANDSHAKE for // yet, if we want to wait for them. - if !p.readyForHandshake && c.waitForHandshake { + if !p.readyForHandshake && c.waitReadyForHandshake { continue } n := p.node.Clone() @@ -234,7 +234,7 @@ func (c *configMaps) peerConfigLocked() []*tailcfg.Node { func (c *configMaps) setWaitForHandshake(wait bool) { c.L.Lock() defer c.L.Unlock() - c.waitForHandshake = wait + c.waitReadyForHandshake = wait } // setAddresses sets the addresses belonging to this node to the given slice. It @@ -413,9 +413,9 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat node: node, lastHandshake: lastHandshake, lost: false, - readyForHandshake: !c.waitForHandshake, + readyForHandshake: !c.waitReadyForHandshake, } - if c.waitForHandshake { + if c.waitReadyForHandshake { lc.readyForHandshakeTimer = c.clock.AfterFunc(5*time.Second, func() { logger.Debug(context.Background(), "ready for handshake timeout") c.peerReadyForHandshakeTimeout(id) From 214d380c5817aeb654fc3c152a1a0ed44ae208b2 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 2 Apr 2024 21:37:16 +0000 Subject: [PATCH 07/14] comments --- codersdk/workspacesdk/connector.go | 8 +- .../workspacesdk/connector_internal_test.go | 2 + tailnet/configmaps.go | 118 ++++++++++++------ tailnet/configmaps_internal_test.go | 86 ++++++++++++- tailnet/conn.go | 9 +- 5 files changed, 176 insertions(+), 47 deletions(-) diff --git a/codersdk/workspacesdk/connector.go b/codersdk/workspacesdk/connector.go index 5c1d9e600aede..37d516a5db8d0 100644 --- a/codersdk/workspacesdk/connector.go +++ b/codersdk/workspacesdk/connector.go @@ -27,6 +27,9 @@ import ( type tailnetConn interface { tailnet.Coordinatee SetDERPMap(derpMap *tailcfg.DERPMap) + // SetTunnelDestination indicates to tailnet that the peer id is a + // destination. + SetTunnelDestination(id uuid.UUID) } // tailnetAPIConnector dials the tailnet API (v2+) and then uses the API with a tailnet.Conn to @@ -75,6 +78,7 @@ func runTailnetAPIConnector( connected: make(chan error, 1), closed: make(chan struct{}), } + conn.SetTunnelDestination(agentID) tac.gracefulCtx, tac.cancelGracefulCtx = context.WithCancel(context.Background()) go tac.manageGracefulTimeout() go tac.run() @@ -86,9 +90,11 @@ func runTailnetAPIConnector( func (tac *tailnetAPIConnector) manageGracefulTimeout() { defer tac.cancelGracefulCtx() <-tac.ctx.Done() + timer := time.NewTimer(time.Second) + defer timer.Stop() select { case <-tac.closed: - case <-time.After(time.Second): + case <-timer.C: } } diff --git a/codersdk/workspacesdk/connector_internal_test.go b/codersdk/workspacesdk/connector_internal_test.go index 57e6f751ff840..9f70891fda258 100644 --- a/codersdk/workspacesdk/connector_internal_test.go +++ b/codersdk/workspacesdk/connector_internal_test.go @@ -102,6 +102,8 @@ func (*fakeTailnetConn) SetNodeCallback(func(*tailnet.Node)) {} func (*fakeTailnetConn) SetDERPMap(*tailcfg.DERPMap) {} +func (*fakeTailnetConn) SetTunnelDestination(uuid.UUID) {} + func newFakeTailnetConn() *fakeTailnetConn { return &fakeTailnetConn{} } diff --git a/tailnet/configmaps.go b/tailnet/configmaps.go index 9a21d184d4ceb..6372ee04946e8 100644 --- a/tailnet/configmaps.go +++ b/tailnet/configmaps.go @@ -56,11 +56,10 @@ type phased struct { type configMaps struct { phased - netmapDirty bool - derpMapDirty bool - filterDirty bool - closing bool - waitReadyForHandshake bool + netmapDirty bool + derpMapDirty bool + filterDirty bool + closing bool engine engineConfigurable static netmap.NetworkMap @@ -218,8 +217,9 @@ func (c *configMaps) peerConfigLocked() []*tailcfg.Node { out := make([]*tailcfg.Node, 0, len(c.peers)) for _, p := range c.peers { // Don't add nodes that we havent received a READY_FOR_HANDSHAKE for - // yet, if we want to wait for them. - if !p.readyForHandshake && c.waitReadyForHandshake { + // yet, if they're a destination. If we received a READY_FOR_HANDSHAKE + // for a peer before we receive their node, the node will be nil. + if (!p.readyForHandshake && p.isDestination) || p.node == nil { continue } n := p.node.Clone() @@ -231,10 +231,17 @@ func (c *configMaps) peerConfigLocked() []*tailcfg.Node { return out } -func (c *configMaps) setWaitForHandshake(wait bool) { +func (c *configMaps) setTunnelDestinaion(id uuid.UUID) { c.L.Lock() defer c.L.Unlock() - c.waitReadyForHandshake = wait + lc, ok := c.peers[id] + if !ok { + lc = &peerLifecycle{ + peerID: id, + } + c.peers[id] = lc + } + lc.isDestination = true } // setAddresses sets the addresses belonging to this node to the given slice. It @@ -343,8 +350,10 @@ func (c *configMaps) updatePeers(updates []*proto.CoordinateResponse_PeerUpdate) // worry about them being up-to-date when handling updates below, and it covers // all peers, not just the ones we got updates about. for _, lc := range c.peers { - if peerStatus, ok := status.Peer[lc.node.Key]; ok { - lc.lastHandshake = peerStatus.LastHandshake + if lc.node != nil { + if peerStatus, ok := status.Peer[lc.node.Key]; ok { + lc.lastHandshake = peerStatus.LastHandshake + } } } @@ -399,7 +408,7 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat // SSH connections don't send packets while idle, so we use keep alives // to avoid random hangs while we set up the connection again after // inactivity. - node.KeepAlive = (statusOk && peerStatus.Active) || (peerOk && lc.node.KeepAlive) + node.KeepAlive = (statusOk && peerStatus.Active) || (peerOk && lc.node != nil && lc.node.KeepAlive) } switch { case !peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: @@ -409,17 +418,14 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat lastHandshake = ps.LastHandshake } lc = &peerLifecycle{ - peerID: id, - node: node, - lastHandshake: lastHandshake, - lost: false, - readyForHandshake: !c.waitReadyForHandshake, - } - if c.waitReadyForHandshake { - lc.readyForHandshakeTimer = c.clock.AfterFunc(5*time.Second, func() { - logger.Debug(context.Background(), "ready for handshake timeout") - c.peerReadyForHandshakeTimeout(id) - }) + peerID: id, + node: node, + lastHandshake: lastHandshake, + lost: false, + // If we're receiving a NODE update for a peer we don't already have + // a lifecycle for, it's likely the source of a tunnel. We don't + // need to wait for a READY_FOR_HANDSHAKE. + readyForHandshake: true, } c.peers[id] = lc logger.Debug(context.Background(), "adding new peer") @@ -428,27 +434,50 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat return lc.readyForHandshake case peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: // update - node.Created = lc.node.Created + if lc.node != nil { + node.Created = lc.node.Created + } dirty = !lc.node.Equal(node) && lc.readyForHandshake lc.node = node lc.lost = false lc.resetTimer() + if lc.isDestination { + if !lc.readyForHandshake { + lc.setReadyForHandshakeTimer(c) + } else { + lc.node.KeepAlive = true + } + } logger.Debug(context.Background(), "node update to existing peer", slog.F("dirty", dirty)) return dirty case peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE: - wasReady := lc.readyForHandshake + dirty := !lc.readyForHandshake lc.readyForHandshake = true if lc.readyForHandshakeTimer != nil { lc.readyForHandshakeTimer.Stop() lc.readyForHandshakeTimer = nil } - lc.node.KeepAlive = true + if lc.node != nil { + dirty = dirty || !lc.node.KeepAlive + lc.node.KeepAlive = true + } logger.Debug(context.Background(), "peer ready for handshake") - return !wasReady + // only force a reconfig if the node populated + return dirty && lc.node != nil case !peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE: - // TODO: should we keep track of ready for handshake messages we get - // from unknown nodes? + // When we receive a READY_FOR_HANDSHAKE for a peer we don't know about, + // we create a peerLifecycle with the peerID and set readyForHandshake + // to true. Eventually we should receive a NODE update for this peer, + // and it'll be programmed into wireguard. logger.Debug(context.Background(), "got peer ready for handshake for unknown peer") + lc = &peerLifecycle{ + peerID: id, + lost: true, + readyForHandshake: true, + } + // Timeout the peer in case we never receive a NODE update for it. + lc.setLostTimer(c) + c.peers[id] = lc return false case !peerOk: // disconnected or lost, but we don't have the node. No op @@ -606,35 +635,50 @@ func (c *configMaps) peerReadyForHandshakeTimeout(peerID uuid.UUID) { } type peerLifecycle struct { - peerID uuid.UUID + peerID uuid.UUID + // isDestination specifies if the peer is a destination, meaning we + // initiated a tunnel to the peer. When the peer is a destination, we do not + // respond to node updates with READY_FOR_HANDSHAKEs, and we wait to program + // the peer into wireguard until we receive a READY_FOR_HANDSHAKE from the + // peer or the timeout is reached. + isDestination bool + // node is the tailcfg.Node for the peer. It may be nil until we receive a + // NODE update for it. node *tailcfg.Node lost bool lastHandshake time.Time - timer *clock.Timer + lostTimer *clock.Timer readyForHandshake bool readyForHandshakeTimer *clock.Timer } func (l *peerLifecycle) resetTimer() { - if l.timer != nil { - l.timer.Stop() - l.timer = nil + if l.lostTimer != nil { + l.lostTimer.Stop() + l.lostTimer = nil } } func (l *peerLifecycle) setLostTimer(c *configMaps) { - if l.timer != nil { - l.timer.Stop() + if l.lostTimer != nil { + l.lostTimer.Stop() } ttl := lostTimeout - c.clock.Since(l.lastHandshake) if ttl <= 0 { ttl = time.Nanosecond } - l.timer = c.clock.AfterFunc(ttl, func() { + l.lostTimer = c.clock.AfterFunc(ttl, func() { c.peerLostTimeout(l.peerID) }) } +func (l *peerLifecycle) setReadyForHandshakeTimer(c *configMaps) { + l.readyForHandshakeTimer = c.clock.AfterFunc(5*time.Second, func() { + c.logger.Debug(context.Background(), "ready for handshake timeout", slog.F("peer_id", l.peerID)) + c.peerReadyForHandshakeTimeout(l.peerID) + }) +} + // prefixesDifferent returns true if the two slices contain different prefixes // where order doesn't matter. func prefixesDifferent(a, b []netip.Prefix) bool { diff --git a/tailnet/configmaps_internal_test.go b/tailnet/configmaps_internal_test.go index 05242b8399f27..895336b9a6356 100644 --- a/tailnet/configmaps_internal_test.go +++ b/tailnet/configmaps_internal_test.go @@ -195,8 +195,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_neverConfigures(t *testing. discoKey := key.NewDisco() uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public()) defer uut.close() - uut.setWaitForHandshake(true) - start := time.Date(2024, time.January, 1, 8, 0, 0, 0, time.UTC) + start := time.Date(2024, time.March, 29, 8, 0, 0, 0, time.UTC) mClock := clock.NewMock() mClock.Set(start) uut.clock = mClock @@ -205,6 +204,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_neverConfigures(t *testing. p1Node := newTestNode(1) p1n, err := NodeToProto(p1Node) require.NoError(t, err) + uut.setTunnelDestinaion(p1ID) // it should not send the peer to the netmap requireNeverConfigures(ctx, t, &uut.phased) @@ -231,6 +231,79 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_neverConfigures(t *testing. _ = testutil.RequireRecvCtx(ctx, t, done) } +func TestConfigMaps_updatePeers_new_waitForHandshake_outOfOrder(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + fEng := newFakeEngineConfigurable() + nodePrivateKey := key.NewNode() + nodeID := tailcfg.NodeID(5) + discoKey := key.NewDisco() + uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public()) + defer uut.close() + start := time.Date(2024, time.March, 29, 8, 0, 0, 0, time.UTC) + mClock := clock.NewMock() + mClock.Set(start) + uut.clock = mClock + + p1ID := uuid.UUID{1} + p1Node := newTestNode(1) + p1n, err := NodeToProto(p1Node) + require.NoError(t, err) + uut.setTunnelDestinaion(p1ID) + + go func() { + <-fEng.status + fEng.statusDone <- struct{}{} + }() + + u2 := []*proto.CoordinateResponse_PeerUpdate{ + { + Id: p1ID[:], + Kind: proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE, + }, + } + uut.updatePeers(u2) + + // it should not send the peer to the netmap yet + + go func() { + <-fEng.status + fEng.statusDone <- struct{}{} + }() + + u1 := []*proto.CoordinateResponse_PeerUpdate{ + { + Id: p1ID[:], + Kind: proto.CoordinateResponse_PeerUpdate_NODE, + Node: p1n, + }, + } + uut.updatePeers(u1) + + // it should now send the peer to the netmap + + nm := testutil.RequireRecvCtx(ctx, t, fEng.setNetworkMap) + r := testutil.RequireRecvCtx(ctx, t, fEng.reconfig) + + require.Len(t, nm.Peers, 1) + n1 := getNodeWithID(t, nm.Peers, 1) + require.Equal(t, "127.3.3.40:1", n1.DERP) + require.Equal(t, p1Node.Endpoints, n1.Endpoints) + require.True(t, n1.KeepAlive) + + // we rely on nmcfg.WGCfg() to convert the netmap to wireguard config, so just + // require the right number of peers. + require.Len(t, r.wg.Peers, 1) + + done := make(chan struct{}) + go func() { + defer close(done) + uut.close() + }() + _ = testutil.RequireRecvCtx(ctx, t, done) +} + func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) @@ -241,8 +314,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) { discoKey := key.NewDisco() uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public()) defer uut.close() - uut.setWaitForHandshake(true) - start := time.Date(2024, time.January, 1, 8, 0, 0, 0, time.UTC) + start := time.Date(2024, time.March, 29, 8, 0, 0, 0, time.UTC) mClock := clock.NewMock() mClock.Set(start) uut.clock = mClock @@ -251,6 +323,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) { p1Node := newTestNode(1) p1n, err := NodeToProto(p1Node) require.NoError(t, err) + uut.setTunnelDestinaion(p1ID) go func() { <-fEng.status @@ -314,7 +387,6 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_timeout(t *testing.T) { discoKey := key.NewDisco() uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public()) defer uut.close() - uut.setWaitForHandshake(true) start := time.Date(2024, time.March, 29, 8, 0, 0, 0, time.UTC) mClock := clock.NewMock() mClock.Set(start) @@ -324,6 +396,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_timeout(t *testing.T) { p1Node := newTestNode(1) p1n, err := NodeToProto(p1Node) require.NoError(t, err) + uut.setTunnelDestinaion(p1ID) go func() { <-fEng.status @@ -453,7 +526,7 @@ func TestConfigMaps_updatePeers_disconnect(t *testing.T) { peerID: p1ID, node: p1tcn, lastHandshake: time.Date(2024, 1, 7, 12, 0, 10, 0, time.UTC), - timer: timer, + lostTimer: timer, } uut.L.Unlock() @@ -1126,6 +1199,7 @@ func requireNeverConfigures(ctx context.Context, t *testing.T, uut *phased) { t.Helper() waiting := make(chan struct{}) go func() { + t.Helper() // ensure that we never configure, and go straight to closed uut.L.Lock() defer uut.L.Unlock() diff --git a/tailnet/conn.go b/tailnet/conn.go index e372d4fce6c8c..c76c5842f3cc1 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -87,8 +87,8 @@ type Options struct { // connections, rather than trying `Upgrade: derp` first and potentially // falling back. This is useful for misbehaving proxies that prevent // fallback due to odd behavior, like Azure App Proxy. - DERPForceWebSockets bool - ShouldWaitForHandshake bool + DERPForceWebSockets bool + ShouldWaitReadyForHandshake bool // BlockEndpoints specifies whether P2P endpoints are blocked. // If so, only DERPs can establish connections. BlockEndpoints bool @@ -216,7 +216,6 @@ func NewConn(options *Options) (conn *Conn, err error) { nodePrivateKey, magicConn.DiscoPublicKey(), ) - cfgMaps.setWaitForHandshake(options.ShouldWaitForHandshake) cfgMaps.setAddresses(options.Addresses) if options.DERPMap != nil { cfgMaps.setDERPMap(options.DERPMap) @@ -312,6 +311,10 @@ type Conn struct { trafficStats *connstats.Statistics } +func (c *Conn) SetTunnelDestination(id uuid.UUID) { + c.configMaps.setTunnelDestinaion(id) +} + func (c *Conn) GetBlockEndpoints() bool { return c.configMaps.getBlockEndpoints() && c.nodeUpdater.getBlockEndpoints() } From 4aaa0e202efb7151f46de3c06311d33467843675 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 2 Apr 2024 21:52:28 +0000 Subject: [PATCH 08/14] fixup! comments --- codersdk/workspacesdk/connector.go | 3 ++- codersdk/workspacesdk/workspacesdk.go | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/codersdk/workspacesdk/connector.go b/codersdk/workspacesdk/connector.go index 37d516a5db8d0..21be216e39ef4 100644 --- a/codersdk/workspacesdk/connector.go +++ b/codersdk/workspacesdk/connector.go @@ -78,7 +78,8 @@ func runTailnetAPIConnector( connected: make(chan error, 1), closed: make(chan struct{}), } - conn.SetTunnelDestination(agentID) + // TODO: reenable in upstack pr + // conn.SetTunnelDestination(agentID) tac.gracefulCtx, tac.cancelGracefulCtx = context.WithCancel(context.Background()) go tac.manageGracefulTimeout() go tac.run() diff --git a/codersdk/workspacesdk/workspacesdk.go b/codersdk/workspacesdk/workspacesdk.go index 9d0a25173a038..8af29f8f76f1f 100644 --- a/codersdk/workspacesdk/workspacesdk.go +++ b/codersdk/workspacesdk/workspacesdk.go @@ -203,8 +203,6 @@ func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options * DERPForceWebSockets: connInfo.DERPForceWebSockets, Logger: options.Logger, BlockEndpoints: c.client.DisableDirectConnections || options.BlockEndpoints, - // TODO: enable in upstack PR - // ShouldWaitForHandshake: true, }) if err != nil { return nil, xerrors.Errorf("create tailnet: %w", err) From 5660e03d02c73bedada4082d627208a078b5c92a Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 2 Apr 2024 22:37:15 +0000 Subject: [PATCH 09/14] add auth to in-memory coordinator --- tailnet/coordinator.go | 4 +++ tailnet/coordinator_test.go | 62 +++++++++++++++++++++++++++++++-- tailnet/tunnel.go | 6 ++++ tailnet/tunnel_internal_test.go | 15 ++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) diff --git a/tailnet/coordinator.go b/tailnet/coordinator.go index ae6f8a1caad3e..9f629d941c383 100644 --- a/tailnet/coordinator.go +++ b/tailnet/coordinator.go @@ -711,6 +711,10 @@ func (c *core) handleReadyForHandshakeLocked(src *peer, rfhs []*proto.Coordinate return xerrors.Errorf("unable to convert bytes to UUID: %w", err) } + if !c.tunnels.tunnelExists(src.id, dstID) { + return xerrors.Errorf("tunnel does not exist between %s and %s", src.id.String(), dstID.String()) + } + dst, ok := c.peers[dstID] if ok { select { diff --git a/tailnet/coordinator_test.go b/tailnet/coordinator_test.go index a6144ba9c9127..574120b7fb2ad 100644 --- a/tailnet/coordinator_test.go +++ b/tailnet/coordinator_test.go @@ -422,8 +422,28 @@ func TestCoordinator(t *testing.T) { clientID := uuid.New() agentID := uuid.New() - aReq, _ := coordinator.Coordinate(ctx, agentID, agentID.String(), tailnet.AgentCoordinateeAuth{ID: agentID}) - _, cRes := coordinator.Coordinate(ctx, clientID, clientID.String(), tailnet.ClientCoordinateeAuth{AgentID: agentID}) + aReq, aRes := coordinator.Coordinate(ctx, agentID, agentID.String(), tailnet.AgentCoordinateeAuth{ID: agentID}) + cReq, cRes := coordinator.Coordinate(ctx, clientID, clientID.String(), tailnet.ClientCoordinateeAuth{AgentID: agentID}) + + { + nk, err := key.NewNode().Public().MarshalBinary() + require.NoError(t, err) + dk, err := key.NewDisco().Public().MarshalText() + require.NoError(t, err) + cReq <- &proto.CoordinateRequest{UpdateSelf: &proto.CoordinateRequest_UpdateSelf{ + Node: &proto.Node{ + Id: 3, + Key: nk, + Disco: string(dk), + }, + }} + } + + cReq <- &proto.CoordinateRequest{AddTunnel: &proto.CoordinateRequest_Tunnel{ + Id: agentID[:], + }} + + testutil.RequireRecvCtx(ctx, t, aRes) aReq <- &proto.CoordinateRequest{ReadyForHandshake: []*proto.CoordinateRequest_ReadyForHandshake{{ Id: clientID[:], @@ -434,6 +454,44 @@ func TestCoordinator(t *testing.T) { require.Equal(t, proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE, ack.PeerUpdates[0].Kind) require.Equal(t, agentID[:], ack.PeerUpdates[0].Id) }) + + t.Run("AgentAck_NoPermission", func(t *testing.T) { + t.Parallel() + logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug) + coordinator := tailnet.NewCoordinator(logger) + ctx := testutil.Context(t, testutil.WaitShort) + + clientID := uuid.New() + agentID := uuid.New() + + aReq, _ := coordinator.Coordinate(ctx, agentID, agentID.String(), tailnet.AgentCoordinateeAuth{ID: agentID}) + _, _ = coordinator.Coordinate(ctx, clientID, clientID.String(), tailnet.ClientCoordinateeAuth{AgentID: agentID}) + + nk, err := key.NewNode().Public().MarshalBinary() + require.NoError(t, err) + dk, err := key.NewDisco().Public().MarshalText() + require.NoError(t, err) + aReq <- &proto.CoordinateRequest{UpdateSelf: &proto.CoordinateRequest_UpdateSelf{ + Node: &proto.Node{ + Id: 3, + Key: nk, + Disco: string(dk), + }, + }} + + require.Eventually(t, func() bool { + return coordinator.Node(agentID) != nil + }, testutil.WaitShort, testutil.IntervalFast) + + aReq <- &proto.CoordinateRequest{ReadyForHandshake: []*proto.CoordinateRequest_ReadyForHandshake{{ + Id: clientID[:], + }}} + + // The agent node should disappear, indicating it was booted off. + require.Eventually(t, func() bool { + return coordinator.Node(agentID) == nil + }, testutil.WaitShort, testutil.IntervalFast) + }) } // TestCoordinator_AgentUpdateWhileClientConnects tests for regression on diff --git a/tailnet/tunnel.go b/tailnet/tunnel.go index faf7b2ec23906..68b78d4f923df 100644 --- a/tailnet/tunnel.go +++ b/tailnet/tunnel.go @@ -151,6 +151,12 @@ func (s *tunnelStore) findTunnelPeers(id uuid.UUID) []uuid.UUID { return out } +func (s *tunnelStore) tunnelExists(src, dst uuid.UUID) bool { + _, srcOK := s.bySrc[src][dst] + _, dstOK := s.byDst[src][dst] + return srcOK || dstOK +} + func (s *tunnelStore) htmlDebug() []HTMLTunnel { out := make([]HTMLTunnel, 0) for src, dsts := range s.bySrc { diff --git a/tailnet/tunnel_internal_test.go b/tailnet/tunnel_internal_test.go index 3ba7cc4165371..b05871f086e04 100644 --- a/tailnet/tunnel_internal_test.go +++ b/tailnet/tunnel_internal_test.go @@ -43,3 +43,18 @@ func TestTunnelStore_RemoveAll(t *testing.T) { require.Len(t, uut.findTunnelPeers(p2), 0) require.Len(t, uut.findTunnelPeers(p3), 0) } + +func TestTunnelStore_TunnelExists(t *testing.T) { + t.Parallel() + p1 := uuid.UUID{1} + p2 := uuid.UUID{2} + uut := newTunnelStore() + require.False(t, uut.tunnelExists(p1, p2)) + require.False(t, uut.tunnelExists(p2, p1)) + uut.add(p1, p2) + require.True(t, uut.tunnelExists(p1, p2)) + require.True(t, uut.tunnelExists(p2, p1)) + uut.remove(p1, p2) + require.False(t, uut.tunnelExists(p1, p2)) + require.False(t, uut.tunnelExists(p2, p1)) +} From 298655b4e45038e3f6ee076452812dd17bf366f5 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 2 Apr 2024 23:18:50 +0000 Subject: [PATCH 10/14] fixup! add auth to in-memory coordinator --- tailnet/configmaps.go | 30 +++++++++++++++++------------ tailnet/configmaps_internal_test.go | 8 ++++---- tailnet/conn.go | 2 +- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/tailnet/configmaps.go b/tailnet/configmaps.go index 6372ee04946e8..0db046e6af4a2 100644 --- a/tailnet/configmaps.go +++ b/tailnet/configmaps.go @@ -231,7 +231,7 @@ func (c *configMaps) peerConfigLocked() []*tailcfg.Node { return out } -func (c *configMaps) setTunnelDestinaion(id uuid.UUID) { +func (c *configMaps) setTunnelDestination(id uuid.UUID) { c.L.Lock() defer c.L.Unlock() lc, ok := c.peers[id] @@ -542,10 +542,12 @@ func (c *configMaps) peerLostTimeout(id uuid.UUID) { "timeout triggered for peer that is removed from the map") return } - if peerStatus, ok := status.Peer[lc.node.Key]; ok { - lc.lastHandshake = peerStatus.LastHandshake + if lc.node != nil { + if peerStatus, ok := status.Peer[lc.node.Key]; ok { + lc.lastHandshake = peerStatus.LastHandshake + } + logger = logger.With(slog.F("key_id", lc.node.Key.ShortString())) } - logger = logger.With(slog.F("key_id", lc.node.Key.ShortString())) if !lc.lost { logger.Debug(context.Background(), "timeout triggered for peer that is no longer lost") @@ -588,7 +590,7 @@ func (c *configMaps) nodeAddresses(publicKey key.NodePublic) ([]netip.Prefix, bo c.L.Lock() defer c.L.Unlock() for _, lc := range c.peers { - if lc.node.Key == publicKey { + if lc.node != nil && lc.node.Key == publicKey { return lc.node.Addresses, true } } @@ -608,12 +610,16 @@ func (c *configMaps) fillPeerDiagnostics(d *PeerDiagnostics, peerID uuid.UUID) { if !ok { return } + d.ReceivedNode = lc.node - ps, ok := status.Peer[lc.node.Key] - if !ok { - return + if lc.node != nil { + ps, ok := status.Peer[lc.node.Key] + if !ok { + return + } + d.LastWireguardHandshake = ps.LastHandshake } - d.LastWireguardHandshake = ps.LastHandshake + return } func (c *configMaps) peerReadyForHandshakeTimeout(peerID uuid.UUID) { @@ -638,9 +644,9 @@ type peerLifecycle struct { peerID uuid.UUID // isDestination specifies if the peer is a destination, meaning we // initiated a tunnel to the peer. When the peer is a destination, we do not - // respond to node updates with READY_FOR_HANDSHAKEs, and we wait to program - // the peer into wireguard until we receive a READY_FOR_HANDSHAKE from the - // peer or the timeout is reached. + // respond to node updates with `READY_FOR_HANDSHAKE`s, and we wait to + // program the peer into wireguard until we receive a READY_FOR_HANDSHAKE + // from the peer or the timeout is reached. isDestination bool // node is the tailcfg.Node for the peer. It may be nil until we receive a // NODE update for it. diff --git a/tailnet/configmaps_internal_test.go b/tailnet/configmaps_internal_test.go index 895336b9a6356..49171ecf03030 100644 --- a/tailnet/configmaps_internal_test.go +++ b/tailnet/configmaps_internal_test.go @@ -204,7 +204,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_neverConfigures(t *testing. p1Node := newTestNode(1) p1n, err := NodeToProto(p1Node) require.NoError(t, err) - uut.setTunnelDestinaion(p1ID) + uut.setTunnelDestination(p1ID) // it should not send the peer to the netmap requireNeverConfigures(ctx, t, &uut.phased) @@ -250,7 +250,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_outOfOrder(t *testing.T) { p1Node := newTestNode(1) p1n, err := NodeToProto(p1Node) require.NoError(t, err) - uut.setTunnelDestinaion(p1ID) + uut.setTunnelDestination(p1ID) go func() { <-fEng.status @@ -323,7 +323,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) { p1Node := newTestNode(1) p1n, err := NodeToProto(p1Node) require.NoError(t, err) - uut.setTunnelDestinaion(p1ID) + uut.setTunnelDestination(p1ID) go func() { <-fEng.status @@ -396,7 +396,7 @@ func TestConfigMaps_updatePeers_new_waitForHandshake_timeout(t *testing.T) { p1Node := newTestNode(1) p1n, err := NodeToProto(p1Node) require.NoError(t, err) - uut.setTunnelDestinaion(p1ID) + uut.setTunnelDestination(p1ID) go func() { <-fEng.status diff --git a/tailnet/conn.go b/tailnet/conn.go index c76c5842f3cc1..6b127604d01b2 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -312,7 +312,7 @@ type Conn struct { } func (c *Conn) SetTunnelDestination(id uuid.UUID) { - c.configMaps.setTunnelDestinaion(id) + c.configMaps.setTunnelDestination(id) } func (c *Conn) GetBlockEndpoints() bool { From 88416d281cf6f4874e56dfb6a871dccf1d9e3d31 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Wed, 3 Apr 2024 21:14:10 +0000 Subject: [PATCH 11/14] comments --- codersdk/workspacesdk/connector.go | 5 -- tailnet/configmaps.go | 115 +++++++++++++++++------------ tailnet/conn.go | 3 +- tailnet/coordinator.go | 5 ++ tailnet/coordinator_test.go | 11 +++ 5 files changed, 83 insertions(+), 56 deletions(-) diff --git a/codersdk/workspacesdk/connector.go b/codersdk/workspacesdk/connector.go index 21be216e39ef4..7955e8fb33292 100644 --- a/codersdk/workspacesdk/connector.go +++ b/codersdk/workspacesdk/connector.go @@ -27,9 +27,6 @@ import ( type tailnetConn interface { tailnet.Coordinatee SetDERPMap(derpMap *tailcfg.DERPMap) - // SetTunnelDestination indicates to tailnet that the peer id is a - // destination. - SetTunnelDestination(id uuid.UUID) } // tailnetAPIConnector dials the tailnet API (v2+) and then uses the API with a tailnet.Conn to @@ -78,8 +75,6 @@ func runTailnetAPIConnector( connected: make(chan error, 1), closed: make(chan struct{}), } - // TODO: reenable in upstack pr - // conn.SetTunnelDestination(agentID) tac.gracefulCtx, tac.cancelGracefulCtx = context.WithCancel(context.Background()) go tac.manageGracefulTimeout() go tac.run() diff --git a/tailnet/configmaps.go b/tailnet/configmaps.go index 0db046e6af4a2..13f0b9b0257ba 100644 --- a/tailnet/configmaps.go +++ b/tailnet/configmaps.go @@ -186,7 +186,7 @@ func (c *configMaps) close() { c.L.Lock() defer c.L.Unlock() for _, lc := range c.peers { - lc.resetTimer() + lc.resetLostTimer() } c.closing = true c.Broadcast() @@ -398,17 +398,7 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat return false } logger = logger.With(slog.F("key_id", node.Key.ShortString()), slog.F("node", node)) - peerStatus, statusOk := status.Peer[node.Key] - // Starting KeepAlive messages at the initialization of a connection - // causes a race condition. If we send the handshake before the peer has - // our node, we'll have to wait for 5 seconds before trying again. - // Ideally, the first handshake starts when the user first initiates a - // connection to the peer. After a successful connection we enable - // keep alives to persist the connection and keep it from becoming idle. - // SSH connections don't send packets while idle, so we use keep alives - // to avoid random hangs while we set up the connection again after - // inactivity. - node.KeepAlive = (statusOk && peerStatus.Active) || (peerOk && lc.node != nil && lc.node.KeepAlive) + node.KeepAlive = c.nodeKeepalive(lc, status, node) } switch { case !peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: @@ -422,31 +412,27 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat node: node, lastHandshake: lastHandshake, lost: false, - // If we're receiving a NODE update for a peer we don't already have - // a lifecycle for, it's likely the source of a tunnel. We don't - // need to wait for a READY_FOR_HANDSHAKE. - readyForHandshake: true, } c.peers[id] = lc logger.Debug(context.Background(), "adding new peer") - // since we just got this node, we don't know if it's ready for - // handshakes yet. - return lc.readyForHandshake + return lc.validForWireguard() case peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: // update if lc.node != nil { node.Created = lc.node.Created } - dirty = !lc.node.Equal(node) && lc.readyForHandshake + dirty = !lc.node.Equal(node) lc.node = node + // validForWireguard checks that the node is non-nil, so should be + // called after we update the node. + dirty = dirty && lc.validForWireguard() lc.lost = false - lc.resetTimer() - if lc.isDestination { - if !lc.readyForHandshake { - lc.setReadyForHandshakeTimer(c) - } else { - lc.node.KeepAlive = true - } + lc.resetLostTimer() + if lc.isDestination && !lc.readyForHandshake { + // We received the node of a destination peer before we've received + // their READY_FOR_HANDSHAKE. Set a timer + lc.setReadyForHandshakeTimer(c) + logger.Debug(context.Background(), "setting ready for handshake timeout") } logger.Debug(context.Background(), "node update to existing peer", slog.F("dirty", dirty)) return dirty @@ -455,7 +441,6 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat lc.readyForHandshake = true if lc.readyForHandshakeTimer != nil { lc.readyForHandshakeTimer.Stop() - lc.readyForHandshakeTimer = nil } if lc.node != nil { dirty = dirty || !lc.node.KeepAlive @@ -475,8 +460,6 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat lost: true, readyForHandshake: true, } - // Timeout the peer in case we never receive a NODE update for it. - lc.setLostTimer(c) c.peers[id] = lc return false case !peerOk: @@ -484,7 +467,7 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat logger.Debug(context.Background(), "skipping update for peer we don't recognize") return false case update.Kind == proto.CoordinateResponse_PeerUpdate_DISCONNECTED: - lc.resetTimer() + lc.resetLostTimer() delete(c.peers, id) logger.Debug(context.Background(), "disconnected peer") return true @@ -607,39 +590,58 @@ func (c *configMaps) fillPeerDiagnostics(d *PeerDiagnostics, peerID uuid.UUID) { } } lc, ok := c.peers[peerID] - if !ok { + if !ok || lc.node == nil { return } d.ReceivedNode = lc.node - if lc.node != nil { - ps, ok := status.Peer[lc.node.Key] - if !ok { - return - } - d.LastWireguardHandshake = ps.LastHandshake + ps, ok := status.Peer[lc.node.Key] + if !ok { + return } - return + d.LastWireguardHandshake = ps.LastHandshake } func (c *configMaps) peerReadyForHandshakeTimeout(peerID uuid.UUID) { + logger := c.logger.With(slog.F("peer_id", peerID)) + logger.Debug(context.Background(), "peer ready for handshake timeout") c.L.Lock() defer c.L.Unlock() lc, ok := c.peers[peerID] if !ok { + logger.Debug(context.Background(), + "ready for handshake timeout triggered for peer that is removed from the map") return } - if lc.readyForHandshakeTimer != nil { - wasReady := lc.readyForHandshake - lc.readyForHandshakeTimer = nil - lc.readyForHandshake = true - if !wasReady { - c.netmapDirty = true - c.Broadcast() - } + + wasReady := lc.readyForHandshake + lc.readyForHandshake = true + if !wasReady { + logger.Info(context.Background(), "setting peer ready for handshake after timeout") + c.netmapDirty = true + c.Broadcast() } } +func (*configMaps) nodeKeepalive(lc *peerLifecycle, status *ipnstate.Status, node *tailcfg.Node) bool { + // If the peer is already active, keepalives should be enabled. + if peerStatus, statusOk := status.Peer[node.Key]; statusOk && peerStatus.Active { + return true + } + // If the peer is a destination, we should only enable keepalives if we've + // received the READY_FOR_HANDSHAKE. + if lc != nil && lc.isDestination && lc.readyForHandshake { + return true + } + // If keepalives are already enabled on the node, keep them enabled. + if lc != nil && lc.node != nil && lc.node.KeepAlive { + return true + } + + // If none of the above are true, keepalives should not be enabled. + return false +} + type peerLifecycle struct { peerID uuid.UUID // isDestination specifies if the peer is a destination, meaning we @@ -658,7 +660,7 @@ type peerLifecycle struct { readyForHandshakeTimer *clock.Timer } -func (l *peerLifecycle) resetTimer() { +func (l *peerLifecycle) resetLostTimer() { if l.lostTimer != nil { l.lostTimer.Stop() l.lostTimer = nil @@ -678,13 +680,28 @@ func (l *peerLifecycle) setLostTimer(c *configMaps) { }) } +const readyForHandshakeTimeout = 5 * time.Second + func (l *peerLifecycle) setReadyForHandshakeTimer(c *configMaps) { - l.readyForHandshakeTimer = c.clock.AfterFunc(5*time.Second, func() { + if l.readyForHandshakeTimer != nil { + l.readyForHandshakeTimer.Stop() + } + l.readyForHandshakeTimer = c.clock.AfterFunc(readyForHandshakeTimeout, func() { c.logger.Debug(context.Background(), "ready for handshake timeout", slog.F("peer_id", l.peerID)) c.peerReadyForHandshakeTimeout(l.peerID) }) } +// validForWireguard returns true if the peer is ready to be programmed into +// wireguard. +func (l *peerLifecycle) validForWireguard() bool { + valid := l.node != nil + if l.isDestination { + return valid && l.readyForHandshake + } + return valid +} + // prefixesDifferent returns true if the two slices contain different prefixes // where order doesn't matter. func prefixesDifferent(a, b []netip.Prefix) bool { diff --git a/tailnet/conn.go b/tailnet/conn.go index 6b127604d01b2..d4d58c7cc9231 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -87,8 +87,7 @@ type Options struct { // connections, rather than trying `Upgrade: derp` first and potentially // falling back. This is useful for misbehaving proxies that prevent // fallback due to odd behavior, like Azure App Proxy. - DERPForceWebSockets bool - ShouldWaitReadyForHandshake bool + DERPForceWebSockets bool // BlockEndpoints specifies whether P2P endpoints are blocked. // If so, only DERPs can establish connections. BlockEndpoints bool diff --git a/tailnet/coordinator.go b/tailnet/coordinator.go index 9f629d941c383..b5aa6d186c38a 100644 --- a/tailnet/coordinator.go +++ b/tailnet/coordinator.go @@ -99,6 +99,9 @@ type Coordinatee interface { UpdatePeers([]*proto.CoordinateResponse_PeerUpdate) error SetAllPeersLost() SetNodeCallback(func(*Node)) + // SetTunnelDestination indicates to tailnet that the peer id is a + // destination. + SetTunnelDestination(id uuid.UUID) } type Coordination interface { @@ -212,6 +215,8 @@ func NewRemoteCoordination(logger slog.Logger, respLoopDone: make(chan struct{}), } if tunnelTarget != uuid.Nil { + // TODO: reenable in upstack PR + // c.coordinatee.SetTunnelDestination(tunnelTarget) c.Lock() err := c.protocol.Send(&proto.CoordinateRequest{AddTunnel: &proto.CoordinateRequest_Tunnel{Id: tunnelTarget[:]}}) c.Unlock() diff --git a/tailnet/coordinator_test.go b/tailnet/coordinator_test.go index 574120b7fb2ad..82707d3c67923 100644 --- a/tailnet/coordinator_test.go +++ b/tailnet/coordinator_test.go @@ -848,6 +848,7 @@ type fakeCoordinatee struct { callback func(*tailnet.Node) updates [][]*proto.CoordinateResponse_PeerUpdate setAllPeersLostCalls int + tunnelDestinations map[uuid.UUID]struct{} } func (f *fakeCoordinatee) UpdatePeers(updates []*proto.CoordinateResponse_PeerUpdate) error { @@ -863,6 +864,16 @@ func (f *fakeCoordinatee) SetAllPeersLost() { f.setAllPeersLostCalls++ } +func (f *fakeCoordinatee) SetTunnelDestination(id uuid.UUID) { + f.Lock() + defer f.Unlock() + + if f.tunnelDestinations == nil { + f.tunnelDestinations = map[uuid.UUID]struct{}{} + } + f.tunnelDestinations[id] = struct{}{} +} + func (f *fakeCoordinatee) SetNodeCallback(callback func(*tailnet.Node)) { f.Lock() defer f.Unlock() From bfda37e677f1c604c5f68e164f91339bf1e9a053 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Wed, 3 Apr 2024 21:34:41 +0000 Subject: [PATCH 12/14] make gen --- tailnet/tailnettest/coordinateemock.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tailnet/tailnettest/coordinateemock.go b/tailnet/tailnettest/coordinateemock.go index 51f2dd2bceaf7..c06243685a6f6 100644 --- a/tailnet/tailnettest/coordinateemock.go +++ b/tailnet/tailnettest/coordinateemock.go @@ -14,6 +14,7 @@ import ( tailnet "github.com/coder/coder/v2/tailnet" proto "github.com/coder/coder/v2/tailnet/proto" + uuid "github.com/google/uuid" gomock "go.uber.org/mock/gomock" ) @@ -64,6 +65,18 @@ func (mr *MockCoordinateeMockRecorder) SetNodeCallback(arg0 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNodeCallback", reflect.TypeOf((*MockCoordinatee)(nil).SetNodeCallback), arg0) } +// SetTunnelDestination mocks base method. +func (m *MockCoordinatee) SetTunnelDestination(arg0 uuid.UUID) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetTunnelDestination", arg0) +} + +// SetTunnelDestination indicates an expected call of SetTunnelDestination. +func (mr *MockCoordinateeMockRecorder) SetTunnelDestination(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTunnelDestination", reflect.TypeOf((*MockCoordinatee)(nil).SetTunnelDestination), arg0) +} + // UpdatePeers mocks base method. func (m *MockCoordinatee) UpdatePeers(arg0 []*proto.CoordinateResponse_PeerUpdate) error { m.ctrl.T.Helper() From 2ee3c51f362b69600c7f83976cd29fb0ac064690 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Wed, 3 Apr 2024 22:08:57 +0000 Subject: [PATCH 13/14] don't kill agent connection on invalid RFH --- tailnet/coordinator.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tailnet/coordinator.go b/tailnet/coordinator.go index b5aa6d186c38a..2dcc34e72502f 100644 --- a/tailnet/coordinator.go +++ b/tailnet/coordinator.go @@ -717,7 +717,12 @@ func (c *core) handleReadyForHandshakeLocked(src *peer, rfhs []*proto.Coordinate } if !c.tunnels.tunnelExists(src.id, dstID) { - return xerrors.Errorf("tunnel does not exist between %s and %s", src.id.String(), dstID.String()) + // We intentionally do not return an error here, since it's + // inherently racy. It's possible for a source to connect, then + // subsequently disconnect before the agent has sent back the RFH. + // Since this could potentially happen to a non-malicious agent, we + // don't want to kill its connection. + continue } dst, ok := c.peers[dstID] From cae734d3a1b29bca23e75df0e35e45736751c628 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 5 Apr 2024 23:02:33 +0000 Subject: [PATCH 14/14] fix no permission test --- tailnet/configmaps.go | 10 ++--- tailnet/coordinator.go | 7 ++++ tailnet/coordinator_test.go | 24 ++---------- tailnet/proto/tailnet.pb.go | 77 +++++++++++++++++++++---------------- tailnet/proto/tailnet.proto | 1 + 5 files changed, 57 insertions(+), 62 deletions(-) diff --git a/tailnet/configmaps.go b/tailnet/configmaps.go index 13f0b9b0257ba..8b3aee1585e47 100644 --- a/tailnet/configmaps.go +++ b/tailnet/configmaps.go @@ -443,8 +443,9 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat lc.readyForHandshakeTimer.Stop() } if lc.node != nil { - dirty = dirty || !lc.node.KeepAlive - lc.node.KeepAlive = true + old := lc.node.KeepAlive + lc.node.KeepAlive = c.nodeKeepalive(lc, status, lc.node) + dirty = dirty || (old != lc.node.KeepAlive) } logger.Debug(context.Background(), "peer ready for handshake") // only force a reconfig if the node populated @@ -457,7 +458,6 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat logger.Debug(context.Background(), "got peer ready for handshake for unknown peer") lc = &peerLifecycle{ peerID: id, - lost: true, readyForHandshake: true, } c.peers[id] = lc @@ -633,10 +633,6 @@ func (*configMaps) nodeKeepalive(lc *peerLifecycle, status *ipnstate.Status, nod if lc != nil && lc.isDestination && lc.readyForHandshake { return true } - // If keepalives are already enabled on the node, keep them enabled. - if lc != nil && lc.node != nil && lc.node.KeepAlive { - return true - } // If none of the above are true, keepalives should not be enabled. return false diff --git a/tailnet/coordinator.go b/tailnet/coordinator.go index 2dcc34e72502f..95f61637f7788 100644 --- a/tailnet/coordinator.go +++ b/tailnet/coordinator.go @@ -722,6 +722,13 @@ func (c *core) handleReadyForHandshakeLocked(src *peer, rfhs []*proto.Coordinate // subsequently disconnect before the agent has sent back the RFH. // Since this could potentially happen to a non-malicious agent, we // don't want to kill its connection. + select { + case src.resps <- &proto.CoordinateResponse{ + Error: fmt.Sprintf("you do not share a tunnel with %q", dstID.String()), + }: + default: + return ErrWouldBlock + } continue } diff --git a/tailnet/coordinator_test.go b/tailnet/coordinator_test.go index 82707d3c67923..c4e269c53c8d9 100644 --- a/tailnet/coordinator_test.go +++ b/tailnet/coordinator_test.go @@ -464,33 +464,15 @@ func TestCoordinator(t *testing.T) { clientID := uuid.New() agentID := uuid.New() - aReq, _ := coordinator.Coordinate(ctx, agentID, agentID.String(), tailnet.AgentCoordinateeAuth{ID: agentID}) + aReq, aRes := coordinator.Coordinate(ctx, agentID, agentID.String(), tailnet.AgentCoordinateeAuth{ID: agentID}) _, _ = coordinator.Coordinate(ctx, clientID, clientID.String(), tailnet.ClientCoordinateeAuth{AgentID: agentID}) - nk, err := key.NewNode().Public().MarshalBinary() - require.NoError(t, err) - dk, err := key.NewDisco().Public().MarshalText() - require.NoError(t, err) - aReq <- &proto.CoordinateRequest{UpdateSelf: &proto.CoordinateRequest_UpdateSelf{ - Node: &proto.Node{ - Id: 3, - Key: nk, - Disco: string(dk), - }, - }} - - require.Eventually(t, func() bool { - return coordinator.Node(agentID) != nil - }, testutil.WaitShort, testutil.IntervalFast) - aReq <- &proto.CoordinateRequest{ReadyForHandshake: []*proto.CoordinateRequest_ReadyForHandshake{{ Id: clientID[:], }}} - // The agent node should disappear, indicating it was booted off. - require.Eventually(t, func() bool { - return coordinator.Node(agentID) == nil - }, testutil.WaitShort, testutil.IntervalFast) + rfhError := testutil.RequireRecvCtx(ctx, t, aRes) + require.NotEmpty(t, rfhError.Error) }) } diff --git a/tailnet/proto/tailnet.pb.go b/tailnet/proto/tailnet.pb.go index 6e9368602bd92..5f623cf2b86ad 100644 --- a/tailnet/proto/tailnet.pb.go +++ b/tailnet/proto/tailnet.pb.go @@ -374,6 +374,7 @@ type CoordinateResponse struct { unknownFields protoimpl.UnknownFields PeerUpdates []*CoordinateResponse_PeerUpdate `protobuf:"bytes,1,rep,name=peer_updates,json=peerUpdates,proto3" json:"peer_updates,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` } func (x *CoordinateResponse) Reset() { @@ -415,6 +416,13 @@ func (x *CoordinateResponse) GetPeerUpdates() []*CoordinateResponse_PeerUpdate { return nil } +func (x *CoordinateResponse) GetError() string { + if x != nil { + return x.Error + } + return "" +} + type DERPMap_HomeParams struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1090,46 +1098,47 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x1a, 0x23, 0x0a, 0x11, 0x52, 0x65, 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, 0xf2, 0x02, 0x0a, 0x12, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, 0x88, 0x03, 0x0a, 0x12, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 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, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x1a, 0x87, 0x02, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, - 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, - 0x65, 0x12, 0x48, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x34, 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, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x22, 0x5b, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x10, 0x4b, - 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, - 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x44, 0x45, 0x10, 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, - 0x32, 0xbe, 0x01, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 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, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x87, 0x02, 0x0a, + 0x0a, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x04, 0x6e, + 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, + 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x48, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x34, 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, 0x2e, 0x50, 0x65, 0x65, 0x72, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x5b, 0x0a, 0x04, 0x4b, 0x69, 0x6e, + 0x64, 0x12, 0x14, 0x0a, 0x10, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x44, 0x45, 0x10, + 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, 0x32, 0xbe, 0x01, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6c, 0x6e, + 0x65, 0x74, 0x12, 0x56, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, + 0x4d, 0x61, 0x70, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, + 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, + 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x43, 0x6f, + 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, + 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/tailnet/proto/tailnet.proto b/tailnet/proto/tailnet.proto index 54230a0ce0131..1e948ebac62da 100644 --- a/tailnet/proto/tailnet.proto +++ b/tailnet/proto/tailnet.proto @@ -96,6 +96,7 @@ message CoordinateResponse { string reason = 4; } repeated PeerUpdate peer_updates = 1; + string error = 2; } service Tailnet {