Help us shape the future of Go! Take the 2026 Go Developer Survey

dmsgclient

package
v1.3.104 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

Package dmsgclient pkg/dmsg/dmsgclient/cli.go c1-net-dmsg

Package dmsgclient pkg/dmsg/dmsgclient/cli_fallback.go c1-net-dmsg

net/http-using dmsg bootstrap helpers — excluded from TinyGo builds (net/http is broken on the js target). The net/http-free fallbackDiscClient that these helpers (and the TinyGo discovery-upgrade path) compose lives in fallback_disc.go.

Package dmsgclient pkg/dmsg/dmsgclient/doc.go c1-net-dmsg

dmsgclient provides the dmsg client constructors used by the CLI tools, the wasm builds and the deployment-adjacent services. Picking the wrong one is a real and recurring bug — see the decision matrix below before adding a caller.

Two axes, not nine constructors

Every constructor here is a point on two axes:

  • HOW SERVERS ARE FOUND: query a dmsg-discovery, or start from a seeded set of server entries (embedded in the deployment config).
  • WHETHER WE PUBLISH: register our own entry in the discovery, so other peers can dial us, or stay seed-only and remain undialable-by-lookup.

The second axis is the one that bites. A seed-only client can reach the peers it was seeded with and nothing else, and nobody can find it. That is correct for a read-only consumer with a fixed set of destinations and wrong for anything that must be dialed.

                  | publishes our entry      | seed-only (no entry)
------------------+--------------------------+---------------------------
discovery-based   | StartDmsg                | —
                  | StartDmsgSelfHostedDisc  |
------------------+--------------------------+---------------------------
seeded / embedded | StartDmsgEmbedded        | StartDmsgEmbeddedForServices
                  | StartDmsgSeeded (with a  | StartDmsgSeeded (with an
                  | discovery address)       | empty discovery address)
------------------+--------------------------+---------------------------
one fixed peer    | StartDmsgDirectWithServers

Deployment services publish no discovery entry

TPD, SD, AR, RF and the route setup nodes deliberately do NOT publish a dmsg-discovery entry — that is what saves every caller a lookup round-trip. A client that dials them must therefore SEED them, or each request fails with "dmsg error 100 - entry is not found in discovery".

This is not hypothetical: the reward server used StartDmsgEmbedded with no seeded service PKs, and every stats panel without a CXO feed to fall back on rendered "unavailable" in production until the PKs were seeded. If a caller both dials deployment services AND needs to be dialable, it wants StartDmsgEmbedded WITH servicePKs — publishing and seeding are independent.

The visor does not use this package

pkg/visor builds its dmsg client inline in init_dmsg.go (direct.GetAllEntries plus dmsgdisc.NewHTTP) rather than calling anything here, so the most exercised setup in the codebase is not the shared one. Converging the two is tracked separately; until then, do not assume a change here reaches the visor, or that visor behavior is reproduced by these constructors.

Unwired constructors

StartDmsgWithSyntheticDiscovery and StartDmsgWithDirectClient currently have no non-test callers. They are kept because they are exported API, but a new caller should be a deliberate choice rather than an accident of name-matching.

Package dmsgclient pkg/dmsg/dmsgclient/exec.go c1-net-dmsg

Package dmsgclient pkg/dmsg/dmsgclient/fallback_disc.go c1-net-dmsg

The fallbackDiscClient is net/http-FREE (it only composes other disc.APIClient values), so it lives here untagged — usable from both the native (dmsghttp-backed) and TinyGo (dmsg-stream-backed) discovery-upgrade paths. The net/http-using bootstrap helpers (StartDmsgWith*, FallbackRoundTripper, cachingDiscClient) stay in cli_fallback.go behind //go:build !tinygo.

Package dmsgclient pkg/dmsg/dmsgclient/flags.go c1-net-dmsg

CLI flag wiring (cobra) — excluded from TinyGo builds. The TinyGo wasm HV drives dmsg programmatically (StartDmsgSeeded), not via cobra flags.

Package dmsgclient pkg/dmsg/dmsgclient/local_relay.go c1-net-dmsg

Bootstrap for a standalone dmsg client that ATTACHES to a visor on the same host instead of dialing dmsg servers itself (dmsg.Client.AttachLocalRelay, and pkg/visor/init_dmsg_relay_local.go for the other end).

The point is the key. Every standalone tool here — dmsgweb, dmsg-socks5, dmsghttp, curl — takes a --sk and runs under a fixed identity that something else in the deployment already knows: a survey whitelist, a service PK written into someone's config, a rewards record. Those keys cannot rotate, so the usual answer to "this process should not hold its own server sessions" (run it under an ephemeral key, or fold it into the visor) is not available. Attaching keeps the key exactly as it is and moves only the transport.

What changes for the process: one session instead of MinSessions (to the visor over a unix socket), no discovery entry at all, and its streams forwarded over the visor's sessions and transports. What does not change: the key it dials under, the key destinations see, and the dmsg API it uses.

Package dmsgclient pkg/dmsg/dmsgclient/seeded.go c1-net-dmsg

Package dmsgclient pkg/dmsg/dmsgclient/seeded_upgrade_native.go c1-net-dmsg

Index

Constants

This section is empty.

Variables

View Source
var (
	// DmsgDiscURL is the dmsg discovery URL
	DmsgDiscURL = dmsg.DiscURL()

	// DmsgDiscAddr is the dmsg discovery dmsg address
	DmsgDiscAddr = dmsg.DiscAddr()

	// DmsgSessions is the default number of sessions i.e. servers to connect to
	DmsgSessions = 2

	// DmsgHTTPPath is the path to the dmsghttp-config.json which overrides embedded defaults
	DmsgHTTPPath string

	// UseDC use dmsg direct client with embedded dmsg server configuration and don't connect to discovery server
	UseDC = false

	// DmsgServerAddr specifies a specific dmsg server to connect through.
	// Format: pk@ip:port (e.g., 02a2d4c3...@45.79.124.73:30082)
	DmsgServerAddr string

	// NoRegister suppresses this tool's own dmsg discovery entry.
	//
	// A discovery entry is an INBOUND-reachability advertisement: "these are
	// the servers you can reach me through". A tool that only ever dials out —
	// dmsgcurl fetching a URL, dmsgip asking for an address, the dmsgweb
	// resolver, the dmsg-socks5 client half — opens no dmsg listener, so its
	// entry names servers for a destination that accepts nothing. It is a write
	// on the discovery, a row in its store and a TTL to expire, for an identity
	// that is usually ephemeral (these tools generate a keypair per run) and
	// gone in seconds.
	//
	// Not registering does NOT make a client undialable, and does not affect
	// its ability to dial or resolve anyone else — see dmsg.Config.NoRegister,
	// where registration and resolution are documented as independent axes.
	//
	// Binaries that DO accept inbound dmsg dials (dmsghttp, dmsgwebsrv,
	// dmsg-socks5 serve) leave this false: their entry is the whole point.
	NoRegister = false

	// DmsgAttach is the local visor dmsg-relay acceptor to attach to instead
	// of dialing dmsg servers: a unix socket path, or "tcp://host:port" for a
	// loopback listener. The tool keeps its own key — that is the whole point
	// — and holds no server sessions and no discovery entry while attached.
	// See StartDmsgLocalRelay.
	DmsgAttach string
)

Functions

func AttachTarget

func AttachTarget(s string) (network, addr string)

AttachTarget splits DmsgAttach into (network, address). A bare path is a unix socket — the default and the one with a filesystem gate; "tcp://" opts into the loopback listener, which the visor only serves when it has an explicit key allowlist.

func ExecName

func ExecName() string

ExecName returns the name of the currently running executable, suitable for use as cobra.Command.Use.

func Execute

func Execute(cmd *cobra.Command)

Execute runs the given cobra command and exits on error.

func InitConfig

func InitConfig() error

InitConfig is used to set command flags for the above variables

func InitDmsgWithFlags

func InitDmsgWithFlags(ctx context.Context, dlog *logging.Logger, pk cipher.PubKey, sk cipher.SecKey, httpClient *http.Client, destination string) (dmsgC *dmsg.Client, stop func(), err error)

InitDmsgWithFlags starts dmsg with flags from the flags package

func InitFlags

func InitFlags(cmd *cobra.Command)

InitFlags is used to set command flags for the above variables.

The plain-HTTP discovery flags (-Z/--http, -U/--disc-url) were removed: the deployment is dmsg-only and the clearnet dmsg-discovery HTTP frontend is gone (404), so connecting to discovery over http no longer works. Discovery is reached over dmsg via -A/--disc-addr (dmsg://<pk>:<port>), which is the default; -B/--direct uses the embedded server set with no discovery at all.

func LocalRelayIdentity

func LocalRelayIdentity(ctx context.Context, network, addr string) (cipher.PubKey, uint16, error)

LocalRelayIdentity reads the acceptor's hello at network/addr and hangs up, returning the visor's public key and the dmsg port its relay is published on.

It creates no dmsg client and therefore no identity: "is there a relay acceptor on this socket, and whose?" is a question an operator asks while setting the socket up, and answering it must not mint a throwaway key.

func NewFallbackDiscClient

func NewFallbackDiscClient(direct, http disc.APIClient, log *logging.Logger) disc.APIClient

NewFallbackDiscClient creates a discovery client that tries direct first, then HTTP. Used by callers that need to dial arbitrary client PKs registered in the real dmsg-discovery while keeping a pre-loaded direct.Client for the bootstrap server list (and any synthetic/seeded entries the caller wants to short-circuit). WRITES no-op (direct) — the non-registering "service" variant.

func NewFallbackRoundTripper

func NewFallbackRoundTripper(ctx context.Context, clients []*dmsg.Client) http.RoundTripper

NewFallbackRoundTripper initializes the fallback round tripper.

func NewRegisteringFallbackDiscClient

func NewRegisteringFallbackDiscClient(direct, http disc.APIClient, log *logging.Logger) disc.APIClient

NewRegisteringFallbackDiscClient is the visor variant: READS resolve direct-first (seeded service/server PKs short-circuit, no HTTP-over-dmsg round-trip), but WRITES (Put/Post/DelEntry) go to the HTTP client so the visor's own entry IS published to the real dmsg-discovery. This lets a SINGLE dmsg client both register itself and resolve bootstrap PKs statically — replacing the two-client (dmsgC discovery + dmsgDC direct) setup whose shared PK self-evicts on the dmsg servers.

func ParseServerAddr

func ParseServerAddr(s string) (*disc.Entry, error)

ParseServerAddr parses the --srv flag value into a disc.Entry. Format: pk@ip:port

func StartDmsg

func StartDmsg(ctx context.Context, dlog *logging.Logger, pk cipher.PubKey, sk cipher.SecKey, httpClient *http.Client, dmsgDisc string, dmsgSessions int) (dmsgC *dmsg.Client, stop func(), err error)

StartDmsg starts dmsg returns a dmsg client for the given dmsg discovery

func StartDmsgDirect

func StartDmsgDirect(ctx context.Context, dlog *logging.Logger, pk cipher.PubKey, sk cipher.SecKey, dmsgDiscAddr string, dmsgSessions int, destination string) (*dmsg.Client, func(), error)

StartDmsgDirect starts dmsg returns a dmsg direct client

func StartDmsgDirectWithServers

func StartDmsgDirectWithServers(ctx context.Context, dlog *logging.Logger, pk cipher.PubKey, sk cipher.SecKey, dmsgDiscAddr string, dmsgServers []*disc.Entry, dmsgSessions int, destination string) (dmsgC *dmsg.Client, stop func(), err error)

StartDmsgDirectWithServers starts a DMSG client using the provided set of DMSG servers. It attempts to connect and validate discovery access via the full server set.

func StartDmsgEmbedded

func StartDmsgEmbedded(ctx context.Context, log *logging.Logger, pk cipher.PubKey, sk cipher.SecKey, preferWS bool, servicePKs ...cipher.PubKey) (*dmsg.Client, func(), error)

Optional servicePKs are seeded as delegated entries, exactly as StartDmsgEmbeddedForServices does, but WITHOUT giving up the discovery upgrade — so the caller still publishes its own entry and stays dialable. Deployment services publish no dmsg-discovery entry of their own by design, so a caller that dials them must seed them or every request pays a lookup that answers "entry is not found in discovery". StartDmsgEmbedded starts a dmsg client seeded from the embedded production dmsg-server set (dmsg.Prod.DmsgServers) and with discovery over dmsg (deployment.Prod.DmsgDiscoveryDmsg) — a self-contained, clearnet-free dmsg bootstrap for native standalone tools (e.g. the reward UI) on a dmsg-only deployment, where the clearnet HTTP discovery is gone. preferWS=false uses TCP.

func StartDmsgEmbeddedForServices

func StartDmsgEmbeddedForServices(ctx context.Context, log *logging.Logger, pk cipher.PubKey, sk cipher.SecKey, preferWS bool, servicePKs ...cipher.PubKey) (*dmsg.Client, func(), error)

StartDmsgEmbeddedForServices starts an embedded-seed dmsg client that talks ONLY to the named services and never publishes an entry to the dmsg-discovery.

StartDmsgEmbedded seeds no service PKs, so it depends on the registering fallback discovery to resolve anything — and that fallback publishes our own entry as a side effect of being installed. For a read-only consumer that already knows every PK it will ever dial, that entry is pure litter: the process is never dialed by anyone, and a fresh key per run leaves an abandoned registration behind (the pattern removed from `hv serve` in #4501).

Passing the service PKs seeds them directly, and passing an empty discovery address skips the upgrade entirely, leaving the client in the seed-only mode StartDmsgSeeded already degrades to on upgrade failure: dialable, but unable to resolve peers it was not told about. That is the correct trade for a consumer with a fixed set of destinations, and the wrong one for anything that needs to reach arbitrary peers — those should keep using StartDmsgEmbedded.

func StartDmsgLocalRelay

func StartDmsgLocalRelay(ctx context.Context, dlog *logging.Logger, pk cipher.PubKey, sk cipher.SecKey, network, addr string) (*dmsg.Client, func(), cipher.PubKey, error)

StartDmsgLocalRelay builds and serves a dmsg client attached to the local visor's relay acceptor at network/addr ("unix" + socket path, or "tcp" + a loopback address), and blocks until it holds the relay session or ctx ends.

The client is RelayOnly and NoRegister, which together are what "attached" means in practice:

  • RelayOnly: once the relay session is up the serve loop is satisfied by it and stops looking for servers, so this process holds exactly one session — and it is the visor beside it, not a slot on a shared dmsg server.
  • NoRegister: nothing to publish. A discovery entry names the servers a client sits on so peers can rendezvous there; an attached client sits on none, and peers reach it through the relay. Publishing an entry naming nothing would be worse than publishing none: it is what a peer would resolve and then fail to use.

The discovery client is an EMPTY direct client rather than a real one. It is not a stub for something missing — it is the correct discovery for a process that neither registers nor resolves: DialStream tries relay sessions before any lookup (see client_dial.go's phase 0), so destinations are reached over the relay, which does its own resolution with the visor's entry cache and the visor's sessions. Handing this client a live discovery instead would make it dial dmsg-discovery over the relay on every lookup to answer a question the relay already answered.

It returns the client, a stop func, and the visor's public key.

func StartDmsgSeeded

func StartDmsgSeeded(ctx context.Context, log *logging.Logger, pk cipher.PubKey, sk cipher.SecKey, seedServers []*disc.Entry, discDmsgAddr string, preferWS bool, servicePKs ...cipher.PubKey) (*dmsg.Client, func(), error)

StartDmsgSeeded starts a dmsg client seeded with explicit server entries and preferring the WebSocket transport — the bootstrap a browser (js/wasm) needs against a dmsg-only deployment where HTTP discovery isn't reachable.

How it bootstraps the chicken-and-egg (the client needs discovery to find servers, but a browser can't reach a dmsg-only discovery until it HAS a server):

  1. A direct.Client preloaded with the seedServers entries lets the dmsg client connect straight to a seed server — over WebSocket, since Config.PreferWS is set and the seed entry carries Server.AddressWS. No HTTP discovery is consulted for this first hop.
  2. Once the session is live, discovery is upgraded to run over dmsg itself (dmsghttp transport → discDmsgAddr, a "dmsg://<disc-pk>:<port>" URL), so the client can resolve arbitrary peers by PK and register its own entry in the real discovery — which is what makes it inbound-reachable (peers dial it by PK and a dmsg server bridges the stream back over the WS session). Pass an empty discDmsgAddr to skip the upgrade (seed-only: the client can be dialed/serve listeners but can't itself resolve new peers).

seedServers must carry Server.AddressWS for the WebSocket dial to be chosen; a browser build has no working TCP/QUIC fallback.

func StartDmsgSelfHostedDisc

func StartDmsgSelfHostedDisc(ctx context.Context, dlog *logging.Logger, pk cipher.PubKey, sk cipher.SecKey, dmsgDiscAddr string, dmsgSessions int) (dmsgC *dmsg.Client, stop func(), err error)

StartDmsgSelfHostedDisc starts a SINGLE dmsg client that reaches the (dmsg-only) discovery over its OWN sessions — instead of the previous default path, which spun up N separate "bootstrap" direct clients sharing this same key just to carry discovery HTTP. A dmsg server permits one session per PK, so those bootstrap clients and the main client kicked each other off every shared server, producing a continuous reconnect/re-dial storm (observed on dmsgweb-surveys at ~30 session events/sec).

How it avoids the bootstrap clients: a direct disc client is preloaded with every dmsg server entry plus a synthetic entry for the discovery server, so this one client can connect to servers AND dial the discovery server without any prior discovery lookup. Discovery HTTP (peer lookups) is then routed over this client's own sessions via dmsghttp. PutEntry is a no-op through the fallback wrapper, so the client doesn't register itself — it only dials out. Reaching direct/hidden services still works through DialStream's existing dmsg-100 fallback (dialViaConnectedServers over the same sessions).

func StartDmsgWithDirectClient

func StartDmsgWithDirectClient(ctx context.Context, dlog *logging.Logger, pk cipher.PubKey, sk cipher.SecKey, dmsgSessions int) (dmsgC *dmsg.Client, stop func(), err error)

StartDmsgWithDirectClient starts dmsg with a fallback discovery client This allows dialing any client including the discovery server which doesn't register itself It uses direct client for known entries (servers, discovery, local client) and falls back to HTTP discovery for unknown entries (arbitrary target clients)

func StartDmsgWithSyntheticDiscovery

func StartDmsgWithSyntheticDiscovery(ctx context.Context, dlog *logging.Logger, pk cipher.PubKey, sk cipher.SecKey, httpClient *http.Client, dmsgDisc string, dmsgSessions int) (dmsgC *dmsg.Client, stop func(), err error)

StartDmsgWithSyntheticDiscovery starts dmsg with a synthetic discovery entry for the discovery server itself

Types

type FallbackRoundTripper

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

FallbackRoundTripper tries multiple DMSG transports until one succeeds.

func (*FallbackRoundTripper) RoundTrip

func (f *FallbackRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip tries each DMSG client in order until a successful response is received.

Jump to

Keyboard shortcuts

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