diag

package
v0.21.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: AGPL-3.0 Imports: 13 Imported by: 0

Documentation

Overview

Package diag implements the agent-side debug surface that runs over the existing QUIC control channel to tunnelproxy.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ArgSpec

type ArgSpec struct {
	Type     ArgType `json:"type"`
	Required bool    `json:"required,omitempty"`
	Default  any     `json:"default,omitempty"`
	Max      any     `json:"max,omitempty"`
	Min      any     `json:"min,omitempty"`
	Desc     string  `json:"description,omitempty"`
}

ArgSpec describes one argument of a Command. It is serialized into the manifest returned by the built-in `agent` command so an operator can discover the surface with a single GET.

type ArgType

type ArgType string

ArgType is the wire-format token for an argument's type.

const (
	ArgTypeString   ArgType = "string"
	ArgTypeInt      ArgType = "int"
	ArgTypeBool     ArgType = "bool"
	ArgTypeDuration ArgType = "duration"
)

type Command

type Command interface {
	// Spec returns the manifest entry for this command. It is called
	// once at registration; the result must be safe to share.
	Spec() Spec

	// Run executes the command. args is the raw `args` field from the
	// Request; implementations decode it as needed. ctx carries the
	// dispatcher-imposed wall-clock ceiling and is cancelled when the
	// stream goes away.
	Run(ctx context.Context, args json.RawMessage, e Emitter) (result any, err error)
}

Command is the contract every probe implements. Non-streaming commands return a JSON-serializable Result and ignore the Emitter. Streaming commands return Result == nil and emit chunks via e until Run returns.

type Dispatcher

type Dispatcher struct {

	// MaxConcurrent caps in-flight commands. Excess Requests get
	// ErrBusy. Default 1 — diag is interactive, not throughput-bound.
	MaxConcurrent int

	// DefaultCeiling is applied to commands whose Spec.CeilingMs is 0.
	DefaultCeiling time.Duration
	// contains filtered or unexported fields
}

Dispatcher reads Request frames from a downstream reader, runs each against its Registry, and writes Response frames to an upstream writer. One Dispatcher per agent ↔ tunnelproxy stream.

The dispatcher does not own the transport — the tunnel package opens the HTTP/3 stream and hands the read/write halves in. This keeps pkg/diag free of any QUIC dependency for testability.

func New

func New(r *Registry) *Dispatcher

New returns a Dispatcher backed by r.

func (*Dispatcher) Run

func (d *Dispatcher) Run(ctx context.Context, down io.Reader, up io.Writer) error

Run drives the dispatch loop until ctx is cancelled or down returns EOF/error. It is safe to call once per stream; concurrent calls on the same Dispatcher share the registry but otherwise do not interact.

Frames on `down` MUST be Request; frames on `up` are written as Response. Both halves use newline-delimited JSON.

type Emitter

type Emitter interface {
	// Chunk emits one streaming frame upstream.
	Chunk(v any) error
}

Emitter is what a streaming Command writes its chunks to. It is supplied by the dispatcher; Commands MUST NOT retain a reference past their Run call.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds the set of registered commands. It is built once at startup and treated as immutable thereafter.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty Registry.

func (*Registry) Lookup

func (r *Registry) Lookup(name string) (Command, bool)

Lookup returns the command with the given name and whether it exists.

func (*Registry) Register

func (r *Registry) Register(c Command)

Register adds c to r. Panics on duplicate name — registration is a startup-time operation.

func (*Registry) Specs

func (r *Registry) Specs() []Spec

Specs returns the manifest entries for every registered command, sorted by name. Used by the built-in `agent` command.

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session is the server-side handle for one connected agent's diag stream. It is built by the /diag/rpc handler and consumed by code that wants to drive commands against the agent.

Lifecycle: a Session is alive between Register and the moment the underlying agent stream closes (request body EOF or write failure). After Close, all in-flight Invoke calls receive a final Response frame with Error.Code = ErrInternal.

func NewSession

func NewSession(down io.Writer, up io.Reader) *Session

func (*Session) Close

func (s *Session) Close()

Close terminates the session. Safe to call concurrently and repeatedly.

func (*Session) CloseErr

func (s *Session) CloseErr() error

CloseErr returns the terminal error after Done fires (io.EOF on clean close). Calling before Done is closed returns nil.

func (*Session) Done

func (s *Session) Done() <-chan struct{}

Done returns a channel closed when the session terminates. Read CloseErr afterward to distinguish clean EOF from transport error.

func (*Session) Invoke

func (s *Session) Invoke(ctx context.Context, command string, args json.RawMessage) (<-chan protocol.Response, error)

Invoke sends one Request to the agent and returns a channel that receives every Response frame for that id. The channel is closed after the terminal frame (Result, Error, or Done) or when the session terminates.

args may be nil. ctx cancellation removes the pending entry but does not interrupt the agent — the dispatcher honors its own per-command ceiling.

func (*Session) Start

func (s *Session) Start()

Start launches the demux loop. Done unblocks when it exits.

type Sessions

type Sessions struct {
	// contains filtered or unexported fields
}

Sessions is a process-scoped registry of agent diag sessions keyed by agent identifier (typically the TunnelNode UID).

func NewSessions

func NewSessions() *Sessions

NewSessions returns an empty registry.

func (*Sessions) Lookup

func (r *Sessions) Lookup(agentID string) (*Session, bool)

Lookup returns the session for agentID, if any.

func (*Sessions) Register

func (r *Sessions) Register(agentID string, s *Session)

Register inserts s into the registry under agentID. If an existing session is registered under the same id, it is closed and replaced.

func (*Sessions) Unregister

func (r *Sessions) Unregister(agentID string, s *Session)

Unregister removes the session for agentID iff it equals s. The equality check prevents a stale Unregister call (from a closing old session) from dropping a freshly registered one.

type Spec

type Spec struct {
	Name      string             `json:"name"`
	Desc      string             `json:"description,omitempty"`
	Args      map[string]ArgSpec `json:"args,omitempty"`
	Streams   bool               `json:"streams,omitempty"`
	CeilingMs int                `json:"ceiling_ms"`
}

Spec is the manifest entry for a single Command.

Directories

Path Synopsis
Package commands holds the built-in diag commands.
Package commands holds the built-in diag commands.
Package protocol defines the wire format for the agent diag channel: nd-json frames over one long-lived HTTP/3 stream, demuxed by Id so multiple commands can interleave.
Package protocol defines the wire format for the agent diag channel: nd-json frames over one long-lived HTTP/3 stream, demuxed by Id so multiple commands can interleave.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL