mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: add DRPC tailnet & cli network telemetry (#13687)
This commit is contained in:
@@ -149,6 +149,7 @@ func (c *AgentConn) SSH(ctx context.Context) (*gonet.TCPConn, error) {
|
||||
return nil, xerrors.Errorf("workspace agent not reachable in time: %v", ctx.Err())
|
||||
}
|
||||
|
||||
c.Conn.SendConnectedTelemetry(c.agentAddress(), tailnet.TelemetryApplicationSSH)
|
||||
return c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), AgentSSHPort))
|
||||
}
|
||||
|
||||
@@ -185,6 +186,7 @@ func (c *AgentConn) Speedtest(ctx context.Context, direction speedtest.Direction
|
||||
return nil, xerrors.Errorf("workspace agent not reachable in time: %v", ctx.Err())
|
||||
}
|
||||
|
||||
c.Conn.SendConnectedTelemetry(c.agentAddress(), tailnet.TelemetryApplicationSpeedtest)
|
||||
speedConn, err := c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), AgentSpeedtestPort))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("dial speedtest: %w", err)
|
||||
@@ -378,3 +380,7 @@ func (c *AgentConn) apiClient() *http.Client {
|
||||
func (c *AgentConn) GetPeerDiagnostics() tailnet.PeerDiagnostics {
|
||||
return c.Conn.GetPeerDiagnostics(c.opts.AgentID)
|
||||
}
|
||||
|
||||
func (c *AgentConn) SendDisconnectedTelemetry(application string) {
|
||||
c.Conn.SendDisconnectedTelemetry(c.agentAddress(), application)
|
||||
}
|
||||
|
||||
@@ -7,12 +7,16 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
"nhooyr.io/websocket"
|
||||
"storj.io/drpc"
|
||||
"storj.io/drpc/drpcerr"
|
||||
"tailscale.com/tailcfg"
|
||||
|
||||
"cdr.dev/slog"
|
||||
@@ -38,6 +42,7 @@ type tailnetConn interface {
|
||||
//
|
||||
// 1) run the Coordinate API and pass node information back and forth
|
||||
// 2) stream DERPMap updates and program the Conn
|
||||
// 3) Send network telemetry events
|
||||
//
|
||||
// These functions share the same websocket, and so are combined here so that if we hit a problem
|
||||
// we tear the whole thing down and start over with a new websocket.
|
||||
@@ -58,32 +63,32 @@ type tailnetAPIConnector struct {
|
||||
coordinateURL string
|
||||
dialOptions *websocket.DialOptions
|
||||
conn tailnetConn
|
||||
customDialFn func() (proto.DRPCTailnetClient, error)
|
||||
|
||||
clientMu sync.RWMutex
|
||||
client proto.DRPCTailnetClient
|
||||
|
||||
connected chan error
|
||||
isFirst bool
|
||||
closed chan struct{}
|
||||
|
||||
// Only set to true if we get a response from the server that it doesn't support
|
||||
// network telemetry.
|
||||
telemetryUnavailable atomic.Bool
|
||||
}
|
||||
|
||||
// runTailnetAPIConnector creates and runs a tailnetAPIConnector
|
||||
func runTailnetAPIConnector(
|
||||
ctx context.Context, logger slog.Logger,
|
||||
agentID uuid.UUID, coordinateURL string, dialOptions *websocket.DialOptions,
|
||||
conn tailnetConn,
|
||||
) *tailnetAPIConnector {
|
||||
tac := &tailnetAPIConnector{
|
||||
// Create a new tailnetAPIConnector without running it
|
||||
func newTailnetAPIConnector(ctx context.Context, logger slog.Logger, agentID uuid.UUID, coordinateURL string, dialOptions *websocket.DialOptions) *tailnetAPIConnector {
|
||||
return &tailnetAPIConnector{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
agentID: agentID,
|
||||
coordinateURL: coordinateURL,
|
||||
dialOptions: dialOptions,
|
||||
conn: conn,
|
||||
conn: nil,
|
||||
connected: make(chan error, 1),
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
tac.gracefulCtx, tac.cancelGracefulCtx = context.WithCancel(context.Background())
|
||||
go tac.manageGracefulTimeout()
|
||||
go tac.run()
|
||||
return tac
|
||||
}
|
||||
|
||||
// manageGracefulTimeout allows the gracefulContext to last 1 second longer than the main context
|
||||
@@ -99,21 +104,27 @@ func (tac *tailnetAPIConnector) manageGracefulTimeout() {
|
||||
}
|
||||
}
|
||||
|
||||
func (tac *tailnetAPIConnector) run() {
|
||||
tac.isFirst = true
|
||||
defer close(tac.closed)
|
||||
for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(tac.ctx); {
|
||||
tailnetClient, err := tac.dial()
|
||||
if xerrors.Is(err, &codersdk.Error{}) {
|
||||
return
|
||||
// Runs a tailnetAPIConnector using the provided connection
|
||||
func (tac *tailnetAPIConnector) runConnector(conn tailnetConn) {
|
||||
tac.conn = conn
|
||||
tac.gracefulCtx, tac.cancelGracefulCtx = context.WithCancel(context.Background())
|
||||
go tac.manageGracefulTimeout()
|
||||
go func() {
|
||||
tac.isFirst = true
|
||||
defer close(tac.closed)
|
||||
for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(tac.ctx); {
|
||||
tailnetClient, err := tac.dial()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
tac.clientMu.Lock()
|
||||
tac.client = tailnetClient
|
||||
tac.clientMu.Unlock()
|
||||
tac.logger.Debug(tac.ctx, "obtained tailnet API v2+ client")
|
||||
tac.coordinateAndDERPMap(tailnetClient)
|
||||
tac.logger.Debug(tac.ctx, "tailnet API v2+ connection lost")
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
tac.logger.Debug(tac.ctx, "obtained tailnet API v2+ client")
|
||||
tac.coordinateAndDERPMap(tailnetClient)
|
||||
tac.logger.Debug(tac.ctx, "tailnet API v2+ connection lost")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var permanentErrorStatuses = []int{
|
||||
@@ -123,6 +134,9 @@ var permanentErrorStatuses = []int{
|
||||
}
|
||||
|
||||
func (tac *tailnetAPIConnector) dial() (proto.DRPCTailnetClient, error) {
|
||||
if tac.customDialFn != nil {
|
||||
return tac.customDialFn()
|
||||
}
|
||||
tac.logger.Debug(tac.ctx, "dialing Coder tailnet v2+ API")
|
||||
// nolint:bodyclose
|
||||
ws, res, err := websocket.Dial(tac.ctx, tac.coordinateURL, tac.dialOptions)
|
||||
@@ -194,7 +208,10 @@ func (tac *tailnetAPIConnector) coordinateAndDERPMap(client proto.DRPCTailnetCli
|
||||
// we do NOT want to gracefully disconnect on the coordinate() routine. So, we'll just
|
||||
// close the underlying connection. This will trigger a retry of the control plane in
|
||||
// run().
|
||||
tac.clientMu.Lock()
|
||||
client.DRPCConn().Close()
|
||||
tac.client = nil
|
||||
tac.clientMu.Unlock()
|
||||
// Note that derpMap() logs it own errors, we don't bother here.
|
||||
}
|
||||
}()
|
||||
@@ -258,3 +275,22 @@ func (tac *tailnetAPIConnector) derpMap(client proto.DRPCTailnetClient) error {
|
||||
tac.conn.SetDERPMap(dm)
|
||||
}
|
||||
}
|
||||
|
||||
func (tac *tailnetAPIConnector) SendTelemetryEvent(event *proto.TelemetryEvent) {
|
||||
tac.clientMu.RLock()
|
||||
// We hold the lock for the entire telemetry request, but this would only block
|
||||
// a coordinate retry, and closing the connection.
|
||||
defer tac.clientMu.RUnlock()
|
||||
if tac.client == nil || tac.telemetryUnavailable.Load() {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(tac.ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
_, err := tac.client.PostTelemetry(ctx, &proto.TelemetryRequest{
|
||||
Events: []*proto.TelemetryEvent{event},
|
||||
})
|
||||
if drpcerr.Code(err) == drpcerr.Unimplemented || drpc.ProtocolError.Has(err) && strings.Contains(err.Error(), "unknown rpc: ") {
|
||||
tac.logger.Debug(tac.ctx, "attempted to send telemetry to a server that doesn't support it", slog.Error(err))
|
||||
tac.telemetryUnavailable.Store(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@ import (
|
||||
"github.com/hashicorp/yamux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/xerrors"
|
||||
"nhooyr.io/websocket"
|
||||
"storj.io/drpc"
|
||||
"storj.io/drpc/drpcerr"
|
||||
"tailscale.com/tailcfg"
|
||||
|
||||
"cdr.dev/slog"
|
||||
@@ -75,7 +78,8 @@ func TestTailnetAPIConnector_Disconnects(t *testing.T) {
|
||||
|
||||
fConn := newFakeTailnetConn()
|
||||
|
||||
uut := runTailnetAPIConnector(ctx, logger, agentID, svr.URL, &websocket.DialOptions{}, fConn)
|
||||
uut := newTailnetAPIConnector(ctx, logger, agentID, svr.URL, &websocket.DialOptions{})
|
||||
uut.runConnector(fConn)
|
||||
|
||||
call := testutil.RequireRecvCtx(ctx, t, fCoord.CoordinateCalls)
|
||||
reqTun := testutil.RequireRecvCtx(ctx, t, call.Reqs)
|
||||
@@ -127,7 +131,8 @@ func TestTailnetAPIConnector_UplevelVersion(t *testing.T) {
|
||||
|
||||
fConn := newFakeTailnetConn()
|
||||
|
||||
uut := runTailnetAPIConnector(ctx, logger, agentID, svr.URL, &websocket.DialOptions{}, fConn)
|
||||
uut := newTailnetAPIConnector(ctx, logger, agentID, svr.URL, &websocket.DialOptions{})
|
||||
uut.runConnector(fConn)
|
||||
|
||||
err := testutil.RequireRecvCtx(ctx, t, uut.connected)
|
||||
var sdkErr *codersdk.Error
|
||||
@@ -137,6 +142,144 @@ func TestTailnetAPIConnector_UplevelVersion(t *testing.T) {
|
||||
require.NotEmpty(t, sdkErr.Helper)
|
||||
}
|
||||
|
||||
func TestTailnetAPIConnector_TelemetrySuccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
|
||||
agentID := uuid.UUID{0x55}
|
||||
clientID := uuid.UUID{0x66}
|
||||
fCoord := tailnettest.NewFakeCoordinator()
|
||||
var coord tailnet.Coordinator = fCoord
|
||||
coordPtr := atomic.Pointer[tailnet.Coordinator]{}
|
||||
coordPtr.Store(&coord)
|
||||
derpMapCh := make(chan *tailcfg.DERPMap)
|
||||
defer close(derpMapCh)
|
||||
eventCh := make(chan []*proto.TelemetryEvent, 1)
|
||||
svc, err := tailnet.NewClientService(tailnet.ClientServiceOptions{
|
||||
Logger: logger,
|
||||
CoordPtr: &coordPtr,
|
||||
DERPMapUpdateFrequency: time.Millisecond,
|
||||
DERPMapFn: func() *tailcfg.DERPMap { return <-derpMapCh },
|
||||
NetworkTelemetryHandler: func(batch []*proto.TelemetryEvent) {
|
||||
eventCh <- batch
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
sws, err := websocket.Accept(w, r, nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
ctx, nc := codersdk.WebsocketNetConn(r.Context(), sws, websocket.MessageBinary)
|
||||
err = svc.ServeConnV2(ctx, nc, tailnet.StreamID{
|
||||
Name: "client",
|
||||
ID: clientID,
|
||||
Auth: tailnet.ClientCoordinateeAuth{AgentID: agentID},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}))
|
||||
|
||||
fConn := newFakeTailnetConn()
|
||||
|
||||
uut := newTailnetAPIConnector(ctx, logger, agentID, svr.URL, &websocket.DialOptions{})
|
||||
uut.runConnector(fConn)
|
||||
require.Eventually(t, func() bool {
|
||||
uut.clientMu.Lock()
|
||||
defer uut.clientMu.Unlock()
|
||||
return uut.client != nil
|
||||
}, testutil.WaitShort, testutil.IntervalFast)
|
||||
|
||||
uut.SendTelemetryEvent(&proto.TelemetryEvent{
|
||||
Id: []byte("test event"),
|
||||
})
|
||||
|
||||
testEvents := testutil.RequireRecvCtx(ctx, t, eventCh)
|
||||
|
||||
require.Len(t, testEvents, 1)
|
||||
require.Equal(t, []byte("test event"), testEvents[0].Id)
|
||||
}
|
||||
|
||||
func TestTailnetAPIConnector_TelemetryUnimplemented(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
|
||||
agentID := uuid.UUID{0x55}
|
||||
fConn := newFakeTailnetConn()
|
||||
|
||||
fakeDRPCClient := newFakeDRPCClient()
|
||||
uut := &tailnetAPIConnector{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
agentID: agentID,
|
||||
coordinateURL: "",
|
||||
dialOptions: &websocket.DialOptions{},
|
||||
conn: nil,
|
||||
connected: make(chan error, 1),
|
||||
closed: make(chan struct{}),
|
||||
customDialFn: func() (proto.DRPCTailnetClient, error) {
|
||||
return fakeDRPCClient, nil
|
||||
},
|
||||
}
|
||||
uut.runConnector(fConn)
|
||||
require.Eventually(t, func() bool {
|
||||
uut.clientMu.Lock()
|
||||
defer uut.clientMu.Unlock()
|
||||
return uut.client != nil
|
||||
}, testutil.WaitShort, testutil.IntervalFast)
|
||||
|
||||
fakeDRPCClient.telemeteryErorr = drpcerr.WithCode(xerrors.New("Unimplemented"), 0)
|
||||
uut.SendTelemetryEvent(&proto.TelemetryEvent{})
|
||||
require.False(t, uut.telemetryUnavailable.Load())
|
||||
require.Equal(t, int64(1), atomic.LoadInt64(&fakeDRPCClient.postTelemetryCalls))
|
||||
|
||||
fakeDRPCClient.telemeteryErorr = drpcerr.WithCode(xerrors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
uut.SendTelemetryEvent(&proto.TelemetryEvent{})
|
||||
require.True(t, uut.telemetryUnavailable.Load())
|
||||
uut.SendTelemetryEvent(&proto.TelemetryEvent{})
|
||||
require.Equal(t, int64(2), atomic.LoadInt64(&fakeDRPCClient.postTelemetryCalls))
|
||||
}
|
||||
|
||||
func TestTailnetAPIConnector_TelemetryNotRecognised(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
|
||||
agentID := uuid.UUID{0x55}
|
||||
fConn := newFakeTailnetConn()
|
||||
|
||||
fakeDRPCClient := newFakeDRPCClient()
|
||||
uut := &tailnetAPIConnector{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
agentID: agentID,
|
||||
coordinateURL: "",
|
||||
dialOptions: &websocket.DialOptions{},
|
||||
conn: nil,
|
||||
connected: make(chan error, 1),
|
||||
closed: make(chan struct{}),
|
||||
customDialFn: func() (proto.DRPCTailnetClient, error) {
|
||||
return fakeDRPCClient, nil
|
||||
},
|
||||
}
|
||||
uut.runConnector(fConn)
|
||||
require.Eventually(t, func() bool {
|
||||
uut.clientMu.Lock()
|
||||
defer uut.clientMu.Unlock()
|
||||
return uut.client != nil
|
||||
}, testutil.WaitShort, testutil.IntervalFast)
|
||||
|
||||
fakeDRPCClient.telemeteryErorr = drpc.ProtocolError.New("Protocol Error")
|
||||
uut.SendTelemetryEvent(&proto.TelemetryEvent{})
|
||||
require.False(t, uut.telemetryUnavailable.Load())
|
||||
require.Equal(t, int64(1), atomic.LoadInt64(&fakeDRPCClient.postTelemetryCalls))
|
||||
|
||||
fakeDRPCClient.telemeteryErorr = drpc.ProtocolError.New("unknown rpc: /coder.tailnet.v2.Tailnet/PostTelemetry")
|
||||
uut.SendTelemetryEvent(&proto.TelemetryEvent{})
|
||||
require.True(t, uut.telemetryUnavailable.Load())
|
||||
uut.SendTelemetryEvent(&proto.TelemetryEvent{})
|
||||
require.Equal(t, int64(2), atomic.LoadInt64(&fakeDRPCClient.postTelemetryCalls))
|
||||
}
|
||||
|
||||
type fakeTailnetConn struct{}
|
||||
|
||||
func (*fakeTailnetConn) UpdatePeers([]*proto.CoordinateResponse_PeerUpdate) error {
|
||||
@@ -155,3 +298,123 @@ func (*fakeTailnetConn) SetTunnelDestination(uuid.UUID) {}
|
||||
func newFakeTailnetConn() *fakeTailnetConn {
|
||||
return &fakeTailnetConn{}
|
||||
}
|
||||
|
||||
type fakeDRPCClient struct {
|
||||
postTelemetryCalls int64
|
||||
telemeteryErorr error
|
||||
fakeDRPPCMapStream
|
||||
}
|
||||
|
||||
var _ proto.DRPCTailnetClient = &fakeDRPCClient{}
|
||||
|
||||
func newFakeDRPCClient() *fakeDRPCClient {
|
||||
return &fakeDRPCClient{
|
||||
postTelemetryCalls: 0,
|
||||
fakeDRPPCMapStream: fakeDRPPCMapStream{
|
||||
fakeDRPCStream: fakeDRPCStream{
|
||||
ch: make(chan struct{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Coordinate implements proto.DRPCTailnetClient.
|
||||
func (f *fakeDRPCClient) Coordinate(_ context.Context) (proto.DRPCTailnet_CoordinateClient, error) {
|
||||
return &f.fakeDRPCStream, nil
|
||||
}
|
||||
|
||||
// DRPCConn implements proto.DRPCTailnetClient.
|
||||
func (*fakeDRPCClient) DRPCConn() drpc.Conn {
|
||||
return &fakeDRPCConn{}
|
||||
}
|
||||
|
||||
// PostTelemetry implements proto.DRPCTailnetClient.
|
||||
func (f *fakeDRPCClient) PostTelemetry(_ context.Context, _ *proto.TelemetryRequest) (*proto.TelemetryResponse, error) {
|
||||
atomic.AddInt64(&f.postTelemetryCalls, 1)
|
||||
return nil, f.telemeteryErorr
|
||||
}
|
||||
|
||||
// StreamDERPMaps implements proto.DRPCTailnetClient.
|
||||
func (f *fakeDRPCClient) StreamDERPMaps(_ context.Context, _ *proto.StreamDERPMapsRequest) (proto.DRPCTailnet_StreamDERPMapsClient, error) {
|
||||
return &f.fakeDRPPCMapStream, nil
|
||||
}
|
||||
|
||||
type fakeDRPCConn struct{}
|
||||
|
||||
var _ drpc.Conn = &fakeDRPCConn{}
|
||||
|
||||
// Close implements drpc.Conn.
|
||||
func (*fakeDRPCConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Closed implements drpc.Conn.
|
||||
func (*fakeDRPCConn) Closed() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Invoke implements drpc.Conn.
|
||||
func (*fakeDRPCConn) Invoke(_ context.Context, _ string, _ drpc.Encoding, _ drpc.Message, _ drpc.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewStream implements drpc.Conn.
|
||||
func (*fakeDRPCConn) NewStream(_ context.Context, _ string, _ drpc.Encoding) (drpc.Stream, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type fakeDRPCStream struct {
|
||||
ch chan struct{}
|
||||
}
|
||||
|
||||
var _ proto.DRPCTailnet_CoordinateClient = &fakeDRPCStream{}
|
||||
|
||||
// Close implements proto.DRPCTailnet_CoordinateClient.
|
||||
func (f *fakeDRPCStream) Close() error {
|
||||
close(f.ch)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseSend implements proto.DRPCTailnet_CoordinateClient.
|
||||
func (*fakeDRPCStream) CloseSend() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Context implements proto.DRPCTailnet_CoordinateClient.
|
||||
func (*fakeDRPCStream) Context() context.Context {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MsgRecv implements proto.DRPCTailnet_CoordinateClient.
|
||||
func (*fakeDRPCStream) MsgRecv(_ drpc.Message, _ drpc.Encoding) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MsgSend implements proto.DRPCTailnet_CoordinateClient.
|
||||
func (*fakeDRPCStream) MsgSend(_ drpc.Message, _ drpc.Encoding) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recv implements proto.DRPCTailnet_CoordinateClient.
|
||||
func (f *fakeDRPCStream) Recv() (*proto.CoordinateResponse, error) {
|
||||
<-f.ch
|
||||
return &proto.CoordinateResponse{}, nil
|
||||
}
|
||||
|
||||
// Send implements proto.DRPCTailnet_CoordinateClient.
|
||||
func (f *fakeDRPCStream) Send(*proto.CoordinateRequest) error {
|
||||
<-f.ch
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeDRPPCMapStream struct {
|
||||
fakeDRPCStream
|
||||
}
|
||||
|
||||
var _ proto.DRPCTailnet_StreamDERPMapsClient = &fakeDRPPCMapStream{}
|
||||
|
||||
// Recv implements proto.DRPCTailnet_StreamDERPMapsClient.
|
||||
func (f *fakeDRPPCMapStream) Recv() (*proto.DERPMap, error) {
|
||||
<-f.fakeDRPCStream.ch
|
||||
return &proto.DERPMap{}, nil
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/tailnet"
|
||||
"github.com/coder/coder/v2/tailnet/proto"
|
||||
)
|
||||
|
||||
// AgentIP is a static IPv6 address with the Tailscale prefix that is used to route
|
||||
@@ -181,6 +182,9 @@ type DialAgentOptions struct {
|
||||
// CaptureHook is a callback that captures Disco packets and packets sent
|
||||
// into the tailnet tunnel.
|
||||
CaptureHook capture.Callback
|
||||
// Whether the client will send network telemetry events.
|
||||
// Enable instead of Disable so it's initialized to false (in tests).
|
||||
EnableTelemetry bool
|
||||
}
|
||||
|
||||
func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options *DialAgentOptions) (agentConn *AgentConn, err error) {
|
||||
@@ -196,29 +200,6 @@ func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options *
|
||||
options.BlockEndpoints = true
|
||||
}
|
||||
|
||||
ip := tailnet.IP()
|
||||
var header http.Header
|
||||
if headerTransport, ok := c.client.HTTPClient.Transport.(*codersdk.HeaderTransport); ok {
|
||||
header = headerTransport.Header
|
||||
}
|
||||
conn, err := tailnet.NewConn(&tailnet.Options{
|
||||
Addresses: []netip.Prefix{netip.PrefixFrom(ip, 128)},
|
||||
DERPMap: connInfo.DERPMap,
|
||||
DERPHeader: &header,
|
||||
DERPForceWebSockets: connInfo.DERPForceWebSockets,
|
||||
Logger: options.Logger,
|
||||
BlockEndpoints: c.client.DisableDirectConnections || options.BlockEndpoints,
|
||||
CaptureHook: options.CaptureHook,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("create tailnet: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
headers := make(http.Header)
|
||||
tokenHeader := codersdk.SessionTokenHeader
|
||||
if c.client.SessionTokenHeader != "" {
|
||||
@@ -251,16 +232,44 @@ func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options *
|
||||
q.Add("version", "2.0")
|
||||
coordinateURL.RawQuery = q.Encode()
|
||||
|
||||
connector := runTailnetAPIConnector(ctx, options.Logger,
|
||||
agentID, coordinateURL.String(),
|
||||
connector := newTailnetAPIConnector(ctx, options.Logger, agentID, coordinateURL.String(),
|
||||
&websocket.DialOptions{
|
||||
HTTPClient: c.client.HTTPClient,
|
||||
HTTPHeader: headers,
|
||||
// Need to disable compression to avoid a data-race.
|
||||
CompressionMode: websocket.CompressionDisabled,
|
||||
},
|
||||
conn,
|
||||
)
|
||||
})
|
||||
|
||||
ip := tailnet.IP()
|
||||
var header http.Header
|
||||
if headerTransport, ok := c.client.HTTPClient.Transport.(*codersdk.HeaderTransport); ok {
|
||||
header = headerTransport.Header
|
||||
}
|
||||
var telemetrySink tailnet.TelemetrySink
|
||||
if options.EnableTelemetry {
|
||||
telemetrySink = connector
|
||||
}
|
||||
conn, err := tailnet.NewConn(&tailnet.Options{
|
||||
Addresses: []netip.Prefix{netip.PrefixFrom(ip, 128)},
|
||||
DERPMap: connInfo.DERPMap,
|
||||
DERPHeader: &header,
|
||||
DERPForceWebSockets: connInfo.DERPForceWebSockets,
|
||||
Logger: options.Logger,
|
||||
BlockEndpoints: c.client.DisableDirectConnections || options.BlockEndpoints,
|
||||
CaptureHook: options.CaptureHook,
|
||||
ClientType: proto.TelemetryEvent_CLI,
|
||||
TelemetrySink: telemetrySink,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("create tailnet: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}()
|
||||
connector.runConnector(conn)
|
||||
|
||||
options.Logger.Debug(ctx, "running tailnet API v2+ connector")
|
||||
|
||||
select {
|
||||
|
||||
Reference in New Issue
Block a user